Java tutorial
/** * Copyright 2017 Goldman Sachs. * 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 com.gs.obevo.db.apps.reveng; import java.io.File; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.gs.obevo.api.platform.ChangeType; import com.gs.obevo.db.api.platform.DbPlatform; import com.gs.obevo.db.impl.core.util.MultiLineStringSplitter; import com.gs.obevo.impl.changetypes.UnclassifiedChangeType; import com.gs.obevo.util.FileUtilsCobra; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.eclipse.collections.api.block.function.Function; import org.eclipse.collections.api.block.function.Function0; import org.eclipse.collections.api.block.predicate.Predicate; import org.eclipse.collections.api.block.procedure.Procedure2; import org.eclipse.collections.api.block.procedure.primitive.ObjectIntProcedure; import org.eclipse.collections.api.list.ImmutableList; import org.eclipse.collections.api.list.MutableList; import org.eclipse.collections.api.map.MutableMap; import org.eclipse.collections.api.set.MutableSet; import org.eclipse.collections.impl.block.factory.Predicates; import org.eclipse.collections.impl.factory.Lists; import org.eclipse.collections.impl.factory.Maps; import org.eclipse.collections.impl.factory.Sets; import org.eclipse.collections.impl.list.fixed.ArrayAdapter; public abstract class AbstractDdlReveng { private final DbPlatform platform; private final MultiLineStringSplitter stringSplitter; private final ImmutableList<Predicate<String>> skipPredicates; private final ImmutableList<RevengPattern> revengPatterns; private final Procedure2<ChangeEntry, String> postProcessChange; public AbstractDdlReveng(DbPlatform platform, MultiLineStringSplitter stringSplitter, ImmutableList<Predicate<String>> skipPredicates, ImmutableList<RevengPattern> revengPatterns, Procedure2<ChangeEntry, String> postProcessChange) { this.platform = platform; this.stringSplitter = stringSplitter; this.skipPredicates = skipPredicates; this.revengPatterns = revengPatterns; this.postProcessChange = postProcessChange; } public void reveng(AquaRevengArgs args) { if (args.getInputPath() == null) { printInstructions(args); } else { revengMain(args); } } protected abstract void printInstructions(AquaRevengArgs args); private void revengMain(AquaRevengArgs args) { String schema = args.getDbSchema(); File file = args.getInputPath(); boolean generateBaseline = args.isGenerateBaseline(); File outputDir = args.getOutputPath(); MutableList<ChangeEntry> changeEntries = Lists.mutable.empty(); final MutableList<String> dataLines; if (file.isFile()) { dataLines = FileUtilsCobra.readLines(file); } else { dataLines = ArrayAdapter.adapt(file.listFiles()).select(new Predicate<File>() { @Override public boolean accept(File file) { return file.isFile(); } }).flatCollect(new Function<File, Iterable<String>>() { @Override public Iterable<String> valueOf(File file) { return FileUtilsCobra.readLines(file); } }); } dataLines.forEachWithIndex(new ObjectIntProcedure<String>() { @Override public void value(String line, int i) { if (line.startsWith("--------------------") && dataLines.get(i + 1).startsWith("-- DDL Statements") && dataLines.get(i + 2).startsWith("--------------------")) { dataLines.set(i, ""); dataLines.set(i + 1, ""); dataLines.set(i + 2, ""); } else if (line.startsWith("--------------------") && dataLines.get(i + 2).startsWith("-- DDL Statements") && dataLines.get(i + 4).startsWith("--------------------")) { dataLines.set(i, ""); dataLines.set(i + 1, ""); dataLines.set(i + 2, ""); dataLines.set(i + 3, ""); dataLines.set(i + 4, ""); } else if (line.startsWith("-- DDL Statements for ")) { dataLines.set(i, ""); } } }); String data = dataLines.makeString(SystemUtils.LINE_SEPARATOR); MutableList<String> entries = stringSplitter.valueOf(data); String candidateObject = "UNKNOWN"; ChangeType candidateObjectType = UnclassifiedChangeType.INSTANCE; int selfOrder = 0; int objectOrder = 0; // Find object names MutableSet<String> objectNames = Sets.mutable.empty(); for (String candidateLine : entries) { candidateLine = StringUtils.stripStart(candidateLine, "\r\n \t"); if (StringUtils.isNotBlank(candidateLine) && Predicates.noneOf(skipPredicates).accept(candidateLine)) { candidateLine = candidateLine.replaceAll(schema + "\\.dbo\\.", ""); // sybase ASE candidateLine = candidateLine.replaceAll("'dbo\\.", "'"); // sybase ASE candidateLine = candidateLine.replaceAll("\"" + schema + "\\s*\"\\.", ""); // DB2 candidateLine = candidateLine.replaceAll(schema + "\\.", ""); // alternate DB2 for views candidateLine = removeQuotesFromProcxmode(candidateLine); // sybase ASE RevengPattern chosenRevengPattern = null; String secondaryName = null; for (RevengPattern revengPattern : revengPatterns) { RevengPatternOutput patternMatch = revengPattern.evaluate(candidateLine); if (patternMatch != null) { System.out.println("OBJECT NAME " + patternMatch.getPrimaryName()); objectNames.add(patternMatch.getPrimaryName()); chosenRevengPattern = revengPattern; candidateObject = patternMatch.getPrimaryName(); if (patternMatch.getSecondaryName() != null) { secondaryName = patternMatch.getSecondaryName(); } candidateObjectType = platform.getChangeType(revengPattern.getChangeType()); objectOrder = 0; break; } } } } MutableMap<String, AtomicInteger> countByObject = Maps.mutable.empty(); for (String candidateLine : entries) { try { candidateLine = StringUtils.stripStart(candidateLine, "\r\n \t"); if (StringUtils.isNotBlank(candidateLine) && Predicates.noneOf(skipPredicates).accept(candidateLine)) { for (String objectName : objectNames) { candidateLine = candidateLine.replaceAll(schema + "\\s*\\." + objectName, objectName); // sybase ASE candidateLine = candidateLine.replaceAll( schema.toLowerCase() + "\\s*\\." + objectName.toLowerCase(), objectName.toLowerCase()); // sybase ASE } candidateLine = candidateLine.replaceAll(schema + "\\.dbo\\.", ""); // sybase ASE candidateLine = candidateLine.replaceAll("'dbo\\.", "'"); // sybase ASE candidateLine = candidateLine.replaceAll("\"" + schema + "\\s*\"\\.", ""); // DB2 candidateLine = candidateLine.replaceAll(schema + "\\.", ""); // alternate DB2 for views candidateLine = removeQuotesFromProcxmode(candidateLine); // sybase ASE RevengPattern chosenRevengPattern = null; String secondaryName = null; for (RevengPattern revengPattern : revengPatterns) { RevengPatternOutput patternMatch = revengPattern.evaluate(candidateLine); if (patternMatch != null) { chosenRevengPattern = revengPattern; candidateObject = patternMatch.getPrimaryName(); if (patternMatch.getSecondaryName() != null) { secondaryName = patternMatch.getSecondaryName(); } candidateObjectType = platform.getChangeType(revengPattern.getChangeType()); objectOrder = 0; break; } } AtomicInteger objectOrder2 = countByObject.getIfAbsentPut(candidateObject, new Function0<AtomicInteger>() { @Override public AtomicInteger value() { return new AtomicInteger(0); } }); if (secondaryName == null) { secondaryName = "change" + objectOrder2.getAndIncrement(); } RevEngDestination destination = new RevEngDestination(schema, candidateObjectType, candidateObject, false); String annotation = chosenRevengPattern != null ? chosenRevengPattern.getAnnotation() : null; MutableList<Function<String, LineParseOutput>> postProcessSqls = chosenRevengPattern != null ? chosenRevengPattern.getPostProcessSqls() : Lists.mutable.<Function<String, LineParseOutput>>empty(); for (Function<String, LineParseOutput> postProcessSql : postProcessSqls) { LineParseOutput lineParseOutput = postProcessSql.valueOf(candidateLine); candidateLine = lineParseOutput.getLineOutput(); } ChangeEntry change = new ChangeEntry(destination, candidateLine + "\nGO", secondaryName, annotation, selfOrder++); postProcessChange.value(change, candidateLine); changeEntries.add(change); } } catch (RuntimeException e) { throw new RuntimeException("Failed parsing on statement " + candidateLine, e); } } new RevengWriter().write(platform, changeEntries, outputDir, generateBaseline, RevengWriter.defaultShouldOverwritePredicate(), args.getDbHost(), args.getDbPort(), args.getDbServer()); } /** * TODO move to Sybase subclass. */ public String removeQuotesFromProcxmode(String input) { Pattern compile = Pattern.compile("sp_procxmode '(?:\")(.*?)(?:\")'", Pattern.DOTALL); Matcher matcher = compile.matcher(input); if (matcher.find()) { return matcher.replaceAll("sp_procxmode '" + matcher.group(1) + "'"); } else { return input; } } public static class LineParseOutput { private String lineOutput; private MutableMap<String, String> tokens = Maps.mutable.empty(); public LineParseOutput() { } public LineParseOutput(String lineOutput) { this.lineOutput = lineOutput; } public String getLineOutput() { return lineOutput; } public void setLineOutput(String lineOutput) { this.lineOutput = lineOutput; } public MutableMap<String, String> getTokens() { return tokens; } public void addToken(String key, String value) { tokens.put(key, value); } public LineParseOutput withToken(String key, String value) { tokens.put(key, value); return this; } } public static class RevengPattern { private final String changeType; private final Pattern pattern; private final int primaryNameIndex; private final Integer secondaryNameIndex; private final String annotation; private final MutableList<Function<String, LineParseOutput>> postProcessSqls = Lists.mutable.empty(); public static final Function<RevengPattern, String> TO_CHANGE_TYPE = new Function<RevengPattern, String>() { @Override public String valueOf(RevengPattern revengPattern) { return revengPattern.getChangeType(); } }; public RevengPattern(String changeType, String pattern) { this(changeType, pattern, 1, null, null); } public RevengPattern(String changeType, String pattern, int primaryNameIndex, Integer secondaryNameIndex, String annotation) { this.changeType = changeType; this.pattern = Pattern.compile(pattern, Pattern.DOTALL); this.primaryNameIndex = primaryNameIndex; this.secondaryNameIndex = secondaryNameIndex; this.annotation = annotation; } public String getChangeType() { return changeType; } public Pattern getPattern() { return pattern; } public int getPrimaryNameIndex() { return primaryNameIndex; } public Integer getSecondaryNameIndex() { return secondaryNameIndex; } public String getAnnotation() { return annotation; } public MutableList<Function<String, LineParseOutput>> getPostProcessSqls() { return postProcessSqls; } public RevengPattern withPostProcessSql(Function<String, LineParseOutput> postProcessSql) { this.postProcessSqls.add(postProcessSql); return this; } public RevengPatternOutput evaluate(String input) { Matcher matcher = pattern.matcher(input); if (matcher.find()) { String primaryName = matcher.group(primaryNameIndex); String secondaryName = null; if (secondaryNameIndex != null) { secondaryName = matcher.group(secondaryNameIndex); } return new RevengPatternOutput(primaryName, secondaryName, input); } return null; } @Override public String toString() { return new ToStringBuilder(this).append("changeType", changeType).append("pattern", pattern) .append("primaryNameIndex", primaryNameIndex).append("secondaryNameIndex", secondaryNameIndex) .append("annotation", annotation).append("postProcessSqls", postProcessSqls).toString(); } } public static class RevengPatternOutput { private final String primaryName; private final String secondaryName; private final String revisedLine; public RevengPatternOutput(String primaryName, String secondaryName, String revisedLine) { this.primaryName = primaryName; this.secondaryName = secondaryName; this.revisedLine = revisedLine; } public String getPrimaryName() { return primaryName; } public String getSecondaryName() { return secondaryName; } public String getRevisedLine() { return revisedLine; } } }