[#1171] Add a MatcherStrategy GeneratorStrategy to allow for

configurative regex pattern matching and replacement
This commit is contained in:
lukaseder 2013-09-21 17:51:48 +02:00
parent bf536c5849
commit 1a4ce84c4a
3 changed files with 544 additions and 6 deletions

View File

@ -58,6 +58,7 @@ import org.jooq.tools.StringUtils;
import org.jooq.util.jaxb.Configuration;
import org.jooq.util.jaxb.Generate;
import org.jooq.util.jaxb.Jdbc;
import org.jooq.util.jaxb.Matchers;
import org.jooq.util.jaxb.Property;
import org.jooq.util.jaxb.Schema;
import org.jooq.util.jaxb.Strategy;
@ -181,10 +182,23 @@ public class GenerationTool {
: JavaGenerator.class);
Generator generator = generatorClass.newInstance();
Class<GeneratorStrategy> strategyClass = (Class<GeneratorStrategy>) (!isBlank(g.getStrategy().getName())
? loadClass(trim(g.getStrategy().getName()))
: DefaultGeneratorStrategy.class);
GeneratorStrategy strategy = strategyClass.newInstance();
GeneratorStrategy strategy;
Matchers matchers = g.getStrategy().getMatchers();
if (matchers != null) {
strategy = new MatcherStrategy(matchers);
if (g.getStrategy().getName() != null) {
log.warn("WARNING: Matchers take precedence over custom strategy. Strategy ignored: " +
g.getStrategy().getName());
}
}
else {
Class<GeneratorStrategy> strategyClass = (Class<GeneratorStrategy>) (!isBlank(g.getStrategy().getName())
? loadClass(trim(g.getStrategy().getName()))
: DefaultGeneratorStrategy.class);
strategy = strategyClass.newInstance();
}
generator.setStrategy(strategy);

View File

@ -0,0 +1,348 @@
/**
* Copyright (c) 2009-2013, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name "jOOQ" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.util;
import static java.util.Collections.emptyList;
import static java.util.regex.Pattern.compile;
import static org.jooq.tools.StringUtils.defaultIfEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jooq.tools.StringUtils;
import org.jooq.util.jaxb.MatcherRule;
import org.jooq.util.jaxb.MatcherTransformType;
import org.jooq.util.jaxb.Matchers;
import org.jooq.util.jaxb.Matchers.Fields;
import org.jooq.util.jaxb.Matchers.Routines;
import org.jooq.util.jaxb.Matchers.Schemas;
import org.jooq.util.jaxb.Matchers.Sequences;
import org.jooq.util.jaxb.Matchers.Tables;
/**
* A generator strategy that names objects according to a {@link Matchers}
* configuration object.
*
* @author Lukas Eder
*/
public class MatcherStrategy extends DefaultGeneratorStrategy {
private final Matchers matchers;
public MatcherStrategy(Matchers matchers) {
this.matchers = matchers;
}
/**
* Take a {@link Definition}, try to match its name or qualified name
* against an expression, and apply a rule upon match.
*/
private final String match(Definition definition, String expression, MatcherRule rule) {
if (rule != null) {
return match(definition, expression, rule.getExpression(), rule.getTransform());
}
return null;
}
private final String match(Definition definition, String expression, String ruleExpression) {
return match(definition, expression, ruleExpression, null);
}
private final String match(Definition definition, String expression, String ruleExpression, MatcherTransformType ruleTransformType) {
if (ruleExpression != null) {
Pattern p = compile(defaultIfEmpty(expression, "^.*$").trim());
Matcher m = p.matcher(definition.getName());
if (m.matches()) {
return transform(m.replaceAll(ruleExpression), ruleTransformType);
}
m = p.matcher(definition.getQualifiedName());
if (m.matches()) {
return transform(m.replaceAll(ruleExpression), ruleTransformType);
}
}
return null;
}
private final String transform(String string, MatcherTransformType transform) {
if (transform == null)
return string;
switch (transform) {
case AS_IS:
return string;
case LOWER:
return string.toLowerCase();
case UPPER:
return string.toUpperCase();
case CAMEL:
return StringUtils.toCamelCaseLC(string);
case PASCAL:
return StringUtils.toCamelCase(string);
default:
throw new UnsupportedOperationException("Transform Type not supported : " + transform);
}
}
private final List<Schemas> schemas(Definition definition) {
if (definition instanceof SchemaDefinition) {
return matchers.getSchemas();
}
return emptyList();
}
private final List<Tables> tables(Definition definition) {
if (definition instanceof TableDefinition) {
return matchers.getTables();
}
return emptyList();
}
private final List<Fields> fields(Definition definition) {
if (definition instanceof ColumnDefinition) {
return matchers.getFields();
}
return emptyList();
}
private final List<Routines> routines(Definition definition) {
if (definition instanceof RoutineDefinition) {
return matchers.getRoutines();
}
return emptyList();
}
private final List<Sequences> sequences(Definition definition) {
if (definition instanceof SequenceDefinition) {
return matchers.getSequences();
}
return emptyList();
}
private final List<String> split(String result) {
List<String> list = new ArrayList<String>();
for (String string : result.split(",")) {
list.add(string.trim());
}
return list;
}
@Override
public String getJavaIdentifier(Definition definition) {
for (Schemas schemas : schemas(definition)) {
String result = match(definition, schemas.getExpression(), schemas.getSchemaIdentifier());
if (result != null)
return result;
}
for (Tables tables : tables(definition)) {
String result = match(definition, tables.getExpression(), tables.getTableIdentifier());
if (result != null)
return result;
}
for (Fields fields : fields(definition)) {
String result = match(definition, fields.getExpression(), fields.getFieldIdentifier());
if (result != null)
return result;
}
for (Sequences sequences : sequences(definition)) {
String result = match(definition, sequences.getExpression(), sequences.getSequenceIdentifier());
if (result != null)
return result;
}
// Default to standard behaviour
return super.getJavaIdentifier(definition);
}
@Override
public String getJavaSetterName(Definition definition, Mode mode) {
for (Fields fields : fields(definition)) {
String result = match(definition, fields.getExpression(), fields.getFieldSetter());
if (result != null)
return result;
}
// Default to standard behaviour
return super.getJavaSetterName(definition, mode);
}
@Override
public String getJavaGetterName(Definition definition, Mode mode) {
for (Fields fields : fields(definition)) {
String result = match(definition, fields.getExpression(), fields.getFieldGetter());
if (result != null)
return result;
}
// Default to standard behaviour
return super.getJavaGetterName(definition, mode);
}
@Override
public String getJavaMethodName(Definition definition, Mode mode) {
for (Routines routines : routines(definition)) {
String result = match(definition, routines.getExpression(), routines.getRoutineMethod());
if (result != null)
return result;
}
// Default to standard behaviour
return super.getJavaMethodName(definition, mode);
}
@Override
public String getJavaClassExtends(Definition definition, Mode mode) {
for (Tables tables : tables(definition)) {
String result = null;
switch (mode) {
case POJO: result = match(definition, tables.getExpression(), tables.getPojoExtends()); break;
}
if (result != null)
return result;
}
// Default to standard behaviour
return super.getJavaClassExtends(definition, mode);
}
@Override
public List<String> getJavaClassImplements(Definition definition, Mode mode) {
for (Schemas schemas : schemas(definition)) {
String result = match(definition, schemas.getExpression(), schemas.getSchemaImplements());
if (result != null)
return split(result);
}
for (Tables tables : tables(definition)) {
String result = null;
switch (mode) {
case DEFAULT: result = match(definition, tables.getExpression(), tables.getTableImplements()); break;
case DAO: result = match(definition, tables.getExpression(), tables.getDaoImplements()); break;
case INTERFACE: result = match(definition, tables.getExpression(), tables.getInterfaceImplements()); break;
case POJO: result = match(definition, tables.getExpression(), tables.getPojoImplements()); break;
case RECORD: result = match(definition, tables.getExpression(), tables.getRecordImplements()); break;
}
if (result != null) {
return split(result);
}
}
for (Routines routines : routines(definition)) {
String result = match(definition, routines.getExpression(), routines.getRoutineImplements());
if (result != null)
return split(result);
}
// Default to standard behaviour
return super.getJavaClassImplements(definition, mode);
}
@Override
public String getJavaClassName(Definition definition, Mode mode) {
for (Schemas schemas : schemas(definition)) {
String result = match(definition, schemas.getExpression(), schemas.getSchemaClass());
if (result != null)
return result;
}
for (Tables tables : tables(definition)) {
String result = null;
switch (mode) {
case DEFAULT: result = match(definition, tables.getExpression(), tables.getTableClass()); break;
case DAO: result = match(definition, tables.getExpression(), tables.getDaoClass()); break;
case INTERFACE: result = match(definition, tables.getExpression(), tables.getInterfaceClass()); break;
case POJO: result = match(definition, tables.getExpression(), tables.getPojoClass()); break;
case RECORD: result = match(definition, tables.getExpression(), tables.getRecordClass()); break;
}
if (result != null)
return result;
}
for (Routines routines : routines(definition)) {
String result = match(definition, routines.getExpression(), routines.getRoutineClass());
if (result != null)
return result;
}
// Default to standard behaviour
return super.getJavaClassName(definition, mode);
}
@Override
public String getJavaPackageName(Definition definition, Mode mode) {
return super.getJavaPackageName(definition, mode);
}
@Override
public String getJavaMemberName(Definition definition, Mode mode) {
for (Fields fields : fields(definition)) {
String result = match(definition, fields.getExpression(), fields.getFieldMember());
if (result != null)
return result;
}
// Default to standard behaviour
return super.getJavaMemberName(definition, mode);
}
@Override
public String getOverloadSuffix(Definition definition, Mode mode, String overloadIndex) {
return super.getOverloadSuffix(definition, mode, overloadIndex);
}
}

View File

@ -89,16 +89,192 @@
</complexType>
<complexType name="Strategy">
<all>
<choice>
<!--
The class used to provide a naming strategy for generated source
code. You may override this with your custom naming strategy
-->
<element name="name" type="string" minOccurs="0" maxOccurs="1"
default="org.jooq.util.DefaultGeneratorStrategy" />
<!--
The matcher strategy configuration used when applying an
XML-based strategy
-->
<element name="matchers" type="tns:Matchers" minOccurs="0" maxOccurs="1"/>
</choice>
</complexType>
<!--
Matchers can be used to provide a naming strategy by regular expression configuration.
-->
<complexType name="Matchers">
<all>
<!--
Specify 0..n schema matchers in order to provide a naming strategy for objects
created from schemas.
-->
<element name="schemas" minOccurs="0" maxOccurs="unbounded">
<complexType>
<all>
<!--
This schema matcher applies to all unqualified or qualified schema names
matched by this expression. If left empty, this matcher applies to all schemas.
-->
<element name="expression" type="string" minOccurs="0" maxOccurs="1"/>
<!--
these elements influence the naming of a generated org.jooq.Schema object.
-->
<element name="schemaClass" type="tns:MatcherRule" minOccurs="0" maxOccurs="1" />
<element name="schemaIdentifier" type="tns:MatcherRule" minOccurs="0" maxOccurs="1" />
<element name="schemaImplements" type="string" minOccurs="0" maxOccurs="1" />
</all>
</complexType>
</element>
<!--
Specify 0..n table matchers in order to provide a naming strategy for objects
created from database tables.
-->
<element name="tables" minOccurs="0" maxOccurs="unbounded">
<complexType>
<all>
<!--
This table matcher applies to all unqualified or qualified table names
matched by this expression. If left empty, this matcher applies to all tables.
-->
<element name="expression" type="string" minOccurs="0" maxOccurs="1"/>
<!--
These elements influence the naming of a generated org.jooq.Table object.
-->
<element name="tableClass" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="tableIdentifier" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="tableImplements" type="string" minOccurs="0" maxOccurs="1"/>
<!--
These elements influence the naming of a generated org.jooq.Record object.
-->
<element name="recordClass" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="recordImplements" type="string" minOccurs="0" maxOccurs="1"/>
<!--
These elements influence the naming of a generated interface, implemented by
generated org.jooq.Record objects and by generated POJOs.
-->
<element name="interfaceClass" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="interfaceImplements" type="string" minOccurs="0" maxOccurs="1"/>
<!--
These elements influence the naming of a generated org.jooq.DAO object.
-->
<element name="daoClass" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="daoImplements" type="string" minOccurs="0" maxOccurs="1"/>
<!--
These elements influence the naming of a generated POJO object.
-->
<element name="pojoClass" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="pojoExtends" type="string" minOccurs="0" maxOccurs="1"/>
<element name="pojoImplements" type="string" minOccurs="0" maxOccurs="1"/>
</all>
</complexType>
</element>
<!--
Specify 0..n field matchers in order to provide a naming strategy for objects
created from table fields.
-->
<element name="fields" minOccurs="0" maxOccurs="unbounded">
<complexType>
<all>
<!--
This field matcher applies to all unqualified or qualified field names
matched by this expression. If left empty, this matcher applies to all fields.
-->
<element name="expression" type="string" minOccurs="0" maxOccurs="1"/>
<!--
These elements influence the naming of a generated org.jooq.Field object.
-->
<element name="fieldIdentifier" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="fieldMember" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="fieldSetter" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
<element name="fieldGetter" type="tns:MatcherRule" minOccurs="0" maxOccurs="1"/>
</all>
</complexType>
</element>
<!--
Specify 0..n routine matchers in order to provide a naming strategy for objects
created from routines.
-->
<element name="routines" minOccurs="0" maxOccurs="unbounded">
<complexType>
<all>
<!--
This routine matcher applies to all unqualified or qualified routine names
matched by this expression. If left empty, this matcher applies to all routines.
-->
<element name="expression" type="string" minOccurs="0" maxOccurs="1"/>
<!--
these elements influence the naming of a generated org.jooq.Routine object.
-->
<element name="routineClass" type="tns:MatcherRule" minOccurs="0" maxOccurs="1" />
<element name="routineMethod" type="tns:MatcherRule" minOccurs="0" maxOccurs="1" />
<element name="routineImplements" type="string" minOccurs="0" maxOccurs="1"/>
</all>
</complexType>
</element>
<!--
Specify 0..n sequence matchers in order to provide a naming strategy for objects
created from sequences.
-->
<element name="sequences" minOccurs="0" maxOccurs="unbounded">
<complexType>
<all>
<!--
This sequence matcher applies to all unqualified or qualified sequence names
matched by this expression. If left empty, this matcher applies to all sequences.
-->
<element name="expression" type="string" minOccurs="0" maxOccurs="1"/>
<!--
these elements influence the naming of a generated org.jooq.Routine object.
-->
<element name="sequenceIdentifier" type="tns:MatcherRule" minOccurs="0" maxOccurs="1" />
</all>
</complexType>
</element>
</all>
</complexType>
<complexType name="MatcherRule">
<all>
<element name="transform" type="tns:MatcherTransformType" minOccurs="0" maxOccurs="1"/>
<element name="expression" type="string" minOccurs="1" maxOccurs="1"/>
</all>
</complexType>
<simpleType name="MatcherTransformType">
<restriction base="string">
<enumeration value="AS_IS"/>
<enumeration value="LOWER"/>
<enumeration value="UPPER"/>
<enumeration value="CAMEL"/>
<enumeration value="PASCAL"/>
</restriction>
</simpleType>
<complexType name="Database">
<all>
<!--