/** * Copyright (C) 2009-2012 the original author or authors. * See the notice.md file distributed with this work for additional * information regarding copyright ownership. * * 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 org.fusesource.restygwt.rebind; import java.io.PrintWriter; import java.util.HashSet; import com.google.gwt.core.ext.BadPropertyValueException; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.SelectionProperty; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JParameterizedType; import com.google.gwt.user.rebind.AbstractSourceCreator; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; /** * provides additional helper methods for generating source.. * * @author <a href="http://hiramchirino.com">Hiram Chirino</a> */ public abstract class BaseSourceCreator extends AbstractSourceCreator { private static final int MAX_FILE_NAME_LENGTH = 200; public static final TreeLogger.Type ERROR = TreeLogger.ERROR; public static final TreeLogger.Type WARN = TreeLogger.WARN; public static final TreeLogger.Type INFO = TreeLogger.INFO; public static final TreeLogger.Type TRACE = TreeLogger.TRACE; public static final TreeLogger.Type DEBUG = TreeLogger.DEBUG; public static final TreeLogger.Type SPAM = TreeLogger.SPAM; public static final TreeLogger.Type ALL = TreeLogger.ALL; protected final GeneratorContext context; protected final JClassType source; protected final String packageName; protected final String shortName; protected final String name; protected SourceWriter sourceWriter; private TreeLogger logger; private PrintWriter writer; static final private ThreadLocal<HashSet<String>> GENERATED_CLASSES = new ThreadLocal<HashSet<String>>(); static public HashSet<String> getGeneratedClasses() { HashSet<String> rc = GENERATED_CLASSES.get(); if (rc == null) { rc = new HashSet<String>(); GENERATED_CLASSES.set(rc); } return rc; } static public void clearGeneratedClasses() { GENERATED_CLASSES.set(null); } public static JClassType find(Class<?> type, TreeLogger logger, GeneratorContext context) throws UnableToCompleteException { return RestServiceGenerator.find(logger, context, type.getName().replace('$', '.')); } public BaseSourceCreator(final TreeLogger logger, GeneratorContext context, JClassType source, String suffix) { this.logger = logger; this.context = context; this.source = source; this.packageName = getOpenPackageName( source.getPackage().getName() ); if(source instanceof JParameterizedType) { JParameterizedType ptype = (JParameterizedType)source; StringBuilder builder = new StringBuilder(); for(JClassType type : ptype.getTypeArgs()) { builder.append("__"); builder.append(parametersName2ClassName(type.getParameterizedQualifiedSourceName())); } this.shortName = reduceName(getName( source ) + builder.toString() + suffix,suffix); } else { this.shortName = reduceName(getName( source ) + suffix,suffix); } this.name = packageName + "." + shortName; } //Many filesystems prevent files with names larger than 256 characters. //Lets have class name less than 200 to allow new generators safelly to add more sufixes there if needed private String reduceName(String newClassName,String suffix) { if(newClassName.length()<MAX_FILE_NAME_LENGTH){ return newClassName; } // String sufx = "_Gen_GwtJackEncDec_"; //Lets first try to reduce the package name of the parametrized types // according to parametersName2ClassName parametrized types //Lets find if there are parametrized types String noSufix = newClassName.substring(0,newClassName.length()-suffix.length()); if(newClassName.indexOf("__")>0){ //has generic String primaryName = noSufix.substring(0,noSufix.indexOf("__")); String genericPart = noSufix.substring(noSufix.indexOf("__")+2); StringBuffer genericBuff = new StringBuffer(); String[] eachGeneric = genericPart.split("__"); for(String genericType:eachGeneric){ genericBuff.append("__"); genericBuff.append(reduceType(genericType)); } String finalName = primaryName+genericBuff.toString()+suffix; if(finalName.length()>MAX_FILE_NAME_LENGTH){ //File name is still too long wrapping it out aggressively String baseName = primaryName+genericBuff.toString(); int firstPosition = baseName.indexOf("__"); int lastPosition = baseName.lastIndexOf("__"); String middle = baseName.substring(firstPosition,lastPosition); finalName = baseName.substring(0,firstPosition)+middle.subSequence(0, 4)+"_"+(middle.length()-5)+"_"+middle.substring(middle.length()-9)+baseName.substring(lastPosition)+suffix; return finalName; } return finalName; } //If there is no generic type lets give an error and force the client to reduce className return newClassName; } private String reduceType(String genericType) { if(genericType==null || genericType.indexOf("_")<0){ return genericType; } String pack = genericType.substring(0,genericType.lastIndexOf("_")); String finalName = genericType.substring(genericType.lastIndexOf("_")+1); int packSize = pack.length(); if(packSize>7){ pack = pack.subSequence(0, 2)+Integer.toString((packSize-5))+pack.substring(packSize-3); return pack+"_"+finalName; } return genericType; } public static final String parametersName2ClassName(String parametrizedQualifiedSourceName){ return parametrizedQualifiedSourceName.replace('.', '_').replace("<", "__").replace(">", "__"); } protected String getName( JClassType source ){ if( source.getEnclosingType() != null ){ return getName( source.getEnclosingType() ) + "_" + source.getSimpleSourceName(); } return source.getSimpleSourceName(); } /** * Some packages are protected such that any type we generate in that package can't subsequently be loaded * because of a {@link SecurityException}, for example <code>java.</code> and <code>javax.</code> packages. * <p> * To workaround this issue we add a prefix onto such packages so that the generated code can be loaded * later. The prefix added is <code>open.</code> * * @param name * @return */ private String getOpenPackageName(String name) { if (name.startsWith("java.") || name.startsWith("javax.")) { name = "open."+name; } return name; } protected PrintWriter writer() { HashSet<String> classes = getGeneratedClasses(); if (classes.contains(name)) { return null; } classes.add(name); PrintWriter writer = context.tryCreate(getLogger(), packageName, shortName); if (writer == null) { return null; } return writer; } public interface Branch<R> { R execute() throws UnableToCompleteException; } protected <R> R branch(String msg, Branch<R> callable) throws UnableToCompleteException { return branch(DEBUG, msg, callable); } protected <R> R branch(TreeLogger.Type level, String msg, Branch<R> callable) throws UnableToCompleteException { TreeLogger original = getLogger(); try { logger = getLogger().branch(level, msg); return callable.execute(); } finally { logger = original; } } public BaseSourceCreator i(int i) { if (i == 1) { this.sourceWriter.indent(); } else if (i == -1) { this.sourceWriter.outdent(); } else { throw new IllegalArgumentException(); } return this; } public BaseSourceCreator p(String value) { this.sourceWriter.println(value); // System.out.println(value); return this; } protected BaseSourceCreator p() { this.sourceWriter.println(); return this; } protected TreeLogger getLogger() { return logger; } static String join(int []values, String sep) { StringBuilder sb = new StringBuilder(); for(int i =0; i < values.length; i++) { if( i!=0 ) { sb.append(sep); } sb.append(values[i]); } return sb.toString(); } static String join(Object []values, String sep) { StringBuilder sb = new StringBuilder(); for(int i =0; i < values.length; i++) { if( i!=0 ) { sb.append(sep); } sb.append(values[i]); } return sb.toString(); } final public String create() throws UnableToCompleteException { writer = writer(); if (writer == null) { return name; } logger = getLogger().branch(TreeLogger.DEBUG, "Generating: " + name); ClassSourceFileComposerFactory composerFactory = createComposerFactory(); sourceWriter = composerFactory.createSourceWriter(context, writer); generate(); sourceWriter.commit(getLogger()); return name; } /** * Returns the boolean value of the property or the default value. */ protected static boolean getBooleanProperty(TreeLogger logger, PropertyOracle propertyOracle, String propertyName, boolean defaultValue) { try { SelectionProperty prop = propertyOracle.getSelectionProperty(logger, propertyName); String propVal = prop.getCurrentValue(); return Boolean.parseBoolean(propVal); } catch (BadPropertyValueException e) { // return the default value } return defaultValue; } abstract protected ClassSourceFileComposerFactory createComposerFactory() throws UnableToCompleteException; abstract protected void generate() throws UnableToCompleteException; }