Package co.jirm.core.sql

Source Code of co.jirm.core.sql.SqlPartialParser$DeclarationSql

/**
* Copyright (C) 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package co.jirm.core.sql;

import static co.jirm.core.util.JirmPrecondition.check;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.newLinkedHashMap;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import co.jirm.core.sql.SqlPartialParser.ResourceLoader.CachedResourceLoader;
import co.jirm.core.util.JirmUrlEncodedUtils;
import co.jirm.core.util.ResourceUtils;

import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.LineReader;
import com.google.common.io.Resources;


public class SqlPartialParser {
 
  private static final Pattern tokenPattern = Pattern.compile("^[ \t]*-- ?\\{(.*?)\\}[ \t]*$");
  /*
   * FileDeclaration = SQL and list of HashDeclaration and list of References
   * HashDeclaration = list of References and SQL
   * References = SQL
   */
 
  protected abstract static class DeclarationSql {
    private final Path path;
    private final List<String> declaredSql;
    private final List<ReferenceSql> references;
    protected DeclarationSql(String path, List<String> declaredSql, List<ReferenceSql> references) {
      super();
      this.path = Path.create(path);
      this.declaredSql = declaredSql;
      this.references = references;
    }

    public Path getPath() {
      return path;
    }
   
    public List<String> getDeclaredSql() {
      return declaredSql;
    }
   
    public List<ReferenceSql> getReferences() {
      return references;
    }

    @Override
    public String toString() {
      return "DeclarationSql [path=" + path + ", declaredSql=" + declaredSql + ", references=" + references + "]";
    }
   
    public abstract boolean isHash();
   
    public abstract int getStartIndex();
   
    public List<String> innerExpanded() {
      return isHash() ? declaredSql.subList(1, declaredSql.size() - 1) : declaredSql;
    }
   
    public String join() {
      return Joiner.on("\n").join(innerExpanded());
    }
   
  }
 
  protected static class FileDeclarationSql extends DeclarationSql {
   
    private final Map<String, HashDeclarationSql> hashDeclarations;

    private FileDeclarationSql(String path, List<String> declaredSql, List<ReferenceSql> references, Map<String, HashDeclarationSql> hashDeclarations) {
      super(path, declaredSql, references);
      this.hashDeclarations = hashDeclarations;
    }
   
    public Map<String, HashDeclarationSql> getHashDeclarations() {
      return hashDeclarations;
    }
   
    @Override
    public int getStartIndex() {
      return 0;
    }
   
    public boolean isHash() {
      return false;
    }
   
  }
 
  protected static class HashDeclarationSql extends DeclarationSql {

    private final int startIndex;
    private final int length;
   
    private HashDeclarationSql(String path, List<String> declardSql, List<ReferenceSql> references, int startIndex, int length) {
      super(path, declardSql, references);
      this.startIndex = startIndex;
      this.length = length;
    }
   
   
    public int getStartIndex() {
      return startIndex;
    }
   
    public int getLength() {
      return length;
    }
    @Override
    public boolean isHash() {
      return true;
    }
   
  }
 
  protected static class ReferenceHeader {
    private final Path path;
    private final Map<String, List<String>> parameters;
   
    private ReferenceHeader(Path path, Map<String, List<String>> parameters) {
      super();
      this.path = path;
      this.parameters = parameters;
    }
   
    public static ReferenceHeader parse(String tag) {
      tag = tag.trim();
      String t = tag.startsWith(">") ? tag.substring(1).trim() : tag;
     
      String[] parts = t.split("[ \t]+");
      //final String u;
      //final URI uri;
      final String path;
      final String fragment;
      final String query;
      if (parts.length > 1) {
        check.state( ! t.contains("?"), "Cannot mix space and '?' style");
        URI u = URI.create(parts[0]);
        path = u.getPath();
        fragment = u.getFragment();
        query = parts[1];
      }
      else {
        URI u = URI.create(t);
        path = u.getPath();
        fragment = u.getFragment();
        query = u.getQuery();       
      }
     
      StringBuilder s = new StringBuilder();
      if (path != null) {
        s.append(path);
      }
      if (emptyToNull(query) != null) {
        s.append("?").append(query);
      }
      if(fragment != null) {
        s.append("#").append(fragment);
      }
      URI uri = URI.create(s.toString());
     
      Map<String, List<String>> parameters = ImmutableMap.copyOf(JirmUrlEncodedUtils.parseParameters(uri, "UTF-8"));
     
      String p = (uri.getPath() != null ? uri.getPath() : "")
          (nullToEmpty(uri.getFragment()).isEmpty() ? "" : "#" + uri.getFragment());
      return new ReferenceHeader(Path.create(p), parameters);
    }
   
   
    public Path getPath() {
      return path;
    }
   
    public Map<String, List<String>> getParameters() {
      return parameters;
    }
   
  }
 
  protected static class ReferenceSql {
    private final Path referencePath;
    private final Path currentPath;
    private final List<String> declaredSql;
    private final int startIndex;
    private final int length;
    private final Map<String,List<String>> parameters;
   
    private ReferenceSql(String referencePath, String currentPath, List<String> declaredSql, int startIndex, int length, Map<String,List<String>> parameters) {
      super();
      this.referencePath = Path.create(referencePath);
      this.currentPath = Path.create(currentPath);
      this.declaredSql = declaredSql;
      this.startIndex = startIndex;
      this.length = length;
      this.parameters = parameters;
    }
   
   
    public List<String> getDeclaredSql() {
      return declaredSql;
    }
   
    public Path getCurrentPath() {
      return currentPath;
    }
   
    public Path getReferencePath() {
      return referencePath;
    }
   
    public int getStartIndex() {
      return startIndex;
    }
   
    public int getLength() {
      return length;
    }
   
    public Map<String, List<String>> getParameters() {
      return parameters;
    }
   
    public boolean isSame() {
      List<String> values = getParameters().get("same");
      if (values == null) {
        return false;
      }
      if (values.isEmpty()) {
        return false;
      }
      String v = values.get(values.size() - 1);
      if ("false".equals(v)) {
        return false;
      }
     
      return true;
    }
   
    public List<String> innerExpanded() {
      return declaredSql.subList(1, declaredSql.size() - 1);
    }
   
    public String join() {
      return Joiner.on("\n").join(innerExpanded());
    }
   
  }
 
  public static class ExpandedSql {
    private final List<String> expanded;
    private final DeclarationSql declaration;
   
    private ExpandedSql(List<String> expanded, DeclarationSql declaration) {
      super();
      this.expanded = expanded;
      this.declaration = declaration;
    }
   
    public static ExpandedSql create(DeclarationSql declaration, List<String> expanded) {
      return new ExpandedSql(expanded, declaration);
    }
   
    public List<String> getExpanded() {
      return expanded;
    }
   
    public DeclarationSql getDeclaration() {
      return declaration;
    }
   
    public List<String> innerExpanded() {
      return declaration.isHash() ? expanded.subList(1, expanded.size() - 1) : expanded;
    }
   
    public String join() {
      return Joiner.on("\n").join(innerExpanded());
    }

    @Override
    public String toString() {
      return "ExpandedSql [expanded=" + expanded + ", declaration=" + declaration + "]";
    }
   
   
  }
 
  public static class Path {
    final String path;
    final Optional<String> hash;
    private Path(String path, Optional<String> hash) {
      super();
      this.path = path;
      this.hash = hash;
    }
    public static Path create(String path) {
      String[] parts = path.split("#");
      if (parts.length > 1) {
        return new Path(parts[0], Optional.of(parts[1]));
      }
      return new Path(parts[0], Optional.<String>absent());
    }
   
    public String getPathWithOutHash() {
      return path;
    }
   
    public Optional<String> getHash() {
      return hash;
    }
   
    public String getFullPath() {
      return hash.isPresent() ? path + "#" + hash.get() : path;
    }
   
    public boolean isRelative() {
      return isJustHash() || ! path.startsWith("/");
    }
   
    public boolean isJustHash() {
      return hash.isPresent() && nullToEmpty(path).isEmpty();
    }
   
    public Path parent() {
      check.state(! isJustHash(), "no parent for just hash");
      int index = path.lastIndexOf("/");
      check.state( index > -1, "no parent");
      String parentPath = path.substring(0, index);
      return Path.create(parentPath);
    }
   
    public Path fromRelative(Path p) {
      check.state(p.isRelative(), "path should be relative");
      final Path r;
      if (p.isJustHash()) {
        r = Path.create(this.getPathWithOutHash() + p.getFullPath());
      }
      else {
        r = Path.create(this.parent().getPathWithOutHash() + "/" + p.getFullPath());
      }
      return r;
    }
    @Override
    public String toString() {
      return "Path [" + getFullPath() + "]";
    }
   
   
  }
 
 
 
  private enum PSTATE {
    HASH,
    REFERENCE,
    OTHER,
  }
 
  protected static interface ResourceLoader {
    public String load(String path);
    public static ResourceLoader DEFAULT_LOADER = new ResourceLoader() {
      @Override
      public String load(String path) {
        try {
          String p = check.notNull(path, "path is null");
          check.state(p.startsWith("/"), "path should start with '/' but was: {}", path);
          p = p.substring(1);
          return Resources.toString(Resources.getResource(p),
              Charsets.UTF_8);
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    };
   
    public static class CachedResourceLoader implements ResourceLoader {
      @Override
      public String load(String path) {
        try {
          String p = check.notNull(path, "path is null");
          check.state(p.startsWith("/"), "path should start with '/' but was: {}", path);
          p = p.substring(1);
          return ResourceUtils.getClasspathResourceAsString(p);
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    }
   
    public static ResourceLoader CACHED_LOADER = new CachedResourceLoader();
  }
 
  private static Cache<String, ExpandedSql> fromPathCache = CacheBuilder.newBuilder()
      .maximumSize(100)
      .expireAfterAccess(ResourceUtils.expire, TimeUnit.SECONDS)
      .build();
 
  private static Cache<String, String> fromPathToStringCache = CacheBuilder.newBuilder()
      .maximumSize(100)
      .expireAfterAccess(ResourceUtils.expire, TimeUnit.SECONDS)
      .build();
 
  public static String parseFromPath(final String path) {
    try {
      return fromPathToStringCache.get(path, new Callable<String>() {
        @Override
        public String call() throws Exception {
          return _parseFromPath(path);
        }
      });
    } catch (ExecutionException e) {
      throw new RuntimeException(e);
    }
  }
 
  private static String _parseFromPath(final String path) {
    Map<String, ExpandedSql> c = newLinkedHashMap(fromPathCache.asMap());
    Parser p = new Parser(CachedResourceLoader.CACHED_LOADER, c);
    fromPathCache.asMap().putAll(c);
    return p.expand(path).join();
  }
 
  public static String parseFromPath(Class<?> k , final String path) {
    String resolved;
    resolved = "/" + check.notNull(ResourceUtils.resolvePath(k, path), "bug path failed: {}", path);
    return parseFromPath(resolved);
  }
 
  public static class Parser {

    private final ResourceLoader loader;
    private final Map<String, ExpandedSql> cache;
   
    private Parser(ResourceLoader resourceLoader, Map<String, ExpandedSql> cache) {
      super();
      this.loader = resourceLoader;
      this.cache = cache;
    }
   
    public static Parser create() {
      return new Parser(ResourceLoader.DEFAULT_LOADER, Maps.<String, ExpandedSql> newLinkedHashMap());
    }

    public ExpandedSql expand(String path) {
      return _expand(path, Sets.<String>newHashSet());
    }

    protected ExpandedSql _expand(String path, Set<String> seenPaths) {
      check.state(! seenPaths.contains(path), "Cycle detected for path: {}, paths involved: {}"
          path, seenPaths);
      if (seenPaths.contains(path)) {
      }
      Path p = Path.create(path);
      ExpandedSql loaded = cache.get(p.getFullPath());
      if (loaded != null)
        return loaded;
      boolean hash = p.getHash().isPresent();
      final ExpandedSql e;
      if (hash) {
        ExpandedSql ef = cache.get(p.getPathWithOutHash());
        final FileDeclarationSql fd;
        if (ef != null) {
          fd = (FileDeclarationSql) ef.getDeclaration();
        }
        else {
          fd = loadFile(p.getPathWithOutHash(), loader);
        }
        HashDeclarationSql h = fd.getHashDeclarations().get(p.getHash().get());
        check.notNull(h, "Not Found: {}, Hash #{} not found in file: {}",
            p.getFullPath(), p.getHash().get(), p.getPathWithOutHash());
       
        e = _expand(h, seenPaths);
      }
      else {
        FileDeclarationSql fd = loadFile(path, loader);
        e = _expand(fd, seenPaths);
      }
      cache.put(path, e);
      return e;
    }

    protected ExpandedSql _expand(ReferenceSql f, Set<String> seenPaths) {
      Path cp = f.getCurrentPath();
      Path rp = f.getReferencePath();
      final Path path;
      if (rp.isRelative()) {
        path = cp.fromRelative(rp);
      }
      else {
        path = rp;
      }
      return _expand(path.getFullPath(), seenPaths);
    }

    protected ExpandedSql _expand(DeclarationSql f, Set<String> seenPaths) {

      ImmutableList.Builder<String> sb = ImmutableList.builder();
     
      List<String> lines = f.getDeclaredSql();

      List<ReferenceSql> references = f.getReferences();
      Map<Integer, ExpandedSql> byLine = Maps.newLinkedHashMap();
      Map<Integer, ReferenceSql> referenceSql = Maps.newLinkedHashMap();
     
      for (ReferenceSql r : references) {

        ExpandedSql e = _expand(r, seenPaths);
        r.getStartIndex();
       
        DeclarationSql ds = e.getDeclaration();
        boolean validate = ! r.isSame() || ds.getDeclaredSql().equals(r.getDeclaredSql());
        check.state(validate,
            "Reference '> {}' in {}" +
            " does NOT MATCH declaration {}" +
            "\nREFERENCE:" +
            "\n{}\n" +
            "DECLARATION:" +
            "\n{}\n",
            r.getReferencePath().getFullPath(),
            r.getCurrentPath().getFullPath(),
            ds.getPath().getFullPath(),
            r.getDeclaredSql(),
            
            ds.getDeclaredSql());
        byLine.put(r.getStartIndex(), e);
        referenceSql.put(r.getStartIndex(), r);
      }
     
      for (int i = 0; i < lines.size();) {
        ExpandedSql e = byLine.get(i + f.getStartIndex());
        ReferenceSql r = referenceSql.get(i + f.getStartIndex());
        String line = lines.get(i);
        if (e != null) {
          sb.addAll(e.innerExpanded());
          i += r.getLength();
        }
        else {
          sb.add(line);
          i++;
        }
      }
      return ExpandedSql.create(f, sb.build());
    }


  }
 
 
  private static FileDeclarationSql loadFile(String path, ResourceLoader loader) {
    return processFile(path, loader.load(path));
  }
 
  public static FileDeclarationSql processFile(String path, String sql) {
    try {
      return _processFile(path, sql);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
 
 
  private static FileDeclarationSql _processFile(String path, String sql) throws IOException {
   
    LineReader lr = new LineReader(new StringReader(sql));
    String line;
    ImmutableList.Builder<ReferenceSql> references = ImmutableList.builder();
    ImmutableMap.Builder<String,HashDeclarationSql> hashes = ImmutableMap.builder();
    ImmutableList.Builder<ReferenceSql> hashReferences = ImmutableList.builder();
    Map<String, HashDeclarationSql> nameToHash = newHashMap();
   
    ImmutableList.Builder<String> referenceContent = ImmutableList.builder();
    ImmutableList.Builder<String> hashContent = ImmutableList.builder();
    ImmutableList.Builder<String> fileContent = ImmutableList.builder();

   
    boolean first = true;
    PSTATE state = PSTATE.OTHER;
    PSTATE previousState = PSTATE.OTHER;
   
    String currentHash = null;
    String currentReference = null;
    Map<String,List<String>> currentReferenceParameters = ImmutableMap.of();
    int hashStartIndex = 0;
    int referenceStartIndex = 0;
    int lineIndex = 0;
   
    String PE = "For path: '{}', ";
   
    while ( (line = lr.readLine()) != null) {
      if (first) first = false;
      Matcher m = tokenPattern.matcher(line);
      String tag;
      if (m.matches() && (tag = m.group(1)) != null && ! (tag = tag.trim()).isEmpty()) {
        if (tag != null && tag.startsWith("#")) {
          check.state(state != PSTATE.HASH, PE + "Cannot hash within hash at line {}.", path, lineIndex);
          state = PSTATE.HASH;
          hashContent = ImmutableList.builder();
          hashReferences = ImmutableList.builder();
          currentHash = tag.substring(1).trim();
          HashDeclarationSql existing = nameToHash.get(currentHash);
          if (existing != null) {
            throw check.stateInvalid(
                PE + "Hash: '#{}' already defined line: {}, new definition at line: {}",
                path,
                currentHash, existing.getStartIndex(), lineIndex);
          }
          hashContent.add(line);
          hashStartIndex = lineIndex;
        }
        else if (tag != null && tag.startsWith(">")) {
          check.state(state != PSTATE.REFERENCE, PE + "Cannot reference within reference line {}.", path, lineIndex);
          previousState = state;
          state = PSTATE.REFERENCE;
          referenceContent = ImmutableList.builder();
          ReferenceHeader h = ReferenceHeader.parse(tag);
          currentReference = h.getPath().getFullPath();
          currentReferenceParameters = h.getParameters();
         
          check.state(! currentReference.isEmpty(), PE + "No reference defined", path);
          referenceStartIndex = lineIndex;
          referenceContent.add(line);
          if (previousState == PSTATE.HASH) {
            hashContent.add(line);
          }
        }
        else if (tag != null && tag.equals("<")) {
          check.state(state == PSTATE.REFERENCE, PE + "Invalid close of reference line: {}", path, lineIndex);
          state = previousState;
          int length = lineIndex - referenceStartIndex + 1;
          referenceContent.add(line);
          check.state(length > -1, "length should be greater than -1");
          check.state(length >= 0, PE + "Hash Line index incorrect. Index: {}, Reference start: {}", path, lineIndex, referenceStartIndex);
          ReferenceSql rsql = new ReferenceSql(currentReference,
              path, referenceContent.build(), referenceStartIndex, length, currentReferenceParameters);
          references.add(rsql);
          if (PSTATE.HASH == previousState) {
            hashReferences.add(rsql);
            hashContent.add(line);
          }
        }
        else if (tag != null && tag.startsWith("/")) {
          check.state(state == PSTATE.HASH, PE + "Hash not started or reference not finished line: {}", path, lineIndex);
          String t = tag.substring(1).trim();
          check.state(! t.isEmpty(), PE + "No close hash is defined at line: {}", path, lineIndex);
          check.state(t.equals(currentHash), PE + "Should be current hash tag: {} at line: {}", path, currentHash, lineIndex);
          state = PSTATE.OTHER;
          int length = lineIndex - hashStartIndex + 1;
          hashContent.add(line);
          check.state(length >= 0, PE + "Hash Line index incorrect. Index: {}, Hash start: {}", path, lineIndex, hashStartIndex);
          HashDeclarationSql hash = new HashDeclarationSql(path + "#" + currentHash, hashContent.build(), hashReferences.build(), hashStartIndex, length);
          nameToHash.put(currentHash, hash);
          hashes.put(currentHash, hash);
        }
        else {
          throw check.stateInvalid(PE + "Looks like a bad --{} at line: {}", path, lineIndex);
        }
      }
      else {
        if (PSTATE.HASH == state || PSTATE.HASH == previousState) {
          hashContent.add(line);
        }
        if (PSTATE.REFERENCE == state) {
          referenceContent.add(line);
        }
      }
      fileContent.add(line);
     
      lineIndex++;
    }

    check.state(PSTATE.OTHER == state, "Reference or hash not closed");
    FileDeclarationSql f = new FileDeclarationSql(path, fileContent.build(), references.build(), hashes.build());
    return f;

  }
 

}
TOP

Related Classes of co.jirm.core.sql.SqlPartialParser$DeclarationSql

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.