/*
* Copyright (C) 2014 Red Hat, Inc. and/or its affiliates.
*
* 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.jboss.errai.marshalling.rebind;
import java.io.PrintWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
import org.jboss.errai.codegen.meta.impl.AbstractMetaClass;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaClass;
import org.jboss.errai.common.metadata.RebindUtils;
import org.jboss.errai.marshalling.client.api.MarshallerFramework;
import org.jboss.errai.marshalling.rebind.api.GeneratorMappingContextFactory;
import org.jboss.errai.marshalling.rebind.api.MappingStrategy;
import org.jboss.errai.marshalling.rebind.util.MarshallingGenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
/**
* Generator used to generate marshallers for custom portable types
* independently. In DevMode, generation is deferred until the marshaller is
* actually needed. This is also an incremental generator. It will only generate
* code when a portable type has changed or a new one has been introduced.
* Otherwise, it will use a cached version of the generated marshaller code.
*
* @author Christian Sadilek <csadilek@redhat.com>
*/
public class MarshallerGenerator extends IncrementalGenerator {
private static final Logger log = LoggerFactory.getLogger(MarshallerGenerator.class);
private final String packageName = MarshallerFramework.class.getPackage().getName();
// We're keeping this cache of portable types to compare their contents and
// find out if they have changed since the last refresh.
private static Map<String, MetaClass> cachedPortableTypes = new ConcurrentHashMap<>();
private static Map<String, String> cachedSourceByTypeName = new ConcurrentHashMap<>();
/*
* A version id. Increment this as needed, when structural changes are made to
* the generated output, specifically with respect to it's effect on the
* caching and reuse of previous generator results. Previously cached
* generator results will be invalidated automatically if they were generated
* by a version of this generator with a different version id.
*/
private static final long GENERATOR_VERSION_ID = 1L;
@Override
public RebindResult generateIncrementally(final TreeLogger logger, final GeneratorContext context, final String typeName) throws UnableToCompleteException {
final String fullyQualifiedTypeName = distillTargetTypeName(typeName);
final MetaClass type = MetaClassFactory.get(fullyQualifiedTypeName);
final String className = MarshallerGeneratorFactory.getMarshallerImplClassName(type, true);
final String marshallerTypeName = packageName + "." + className;
final MetaClass cachedType = cachedPortableTypes.get(fullyQualifiedTypeName);
final PrintWriter printWriter = context.tryCreate(logger, packageName, className);
if (printWriter != null) {
if (!RebindUtils.NO_CACHE && cachedType != null && cachedType.hashContent() == type.hashContent()) {
log.debug("Reusing cached marshaller for {}", fullyQualifiedTypeName);
printWriter.append(cachedSourceByTypeName.get(fullyQualifiedTypeName));
context.commit(logger, printWriter);
} else {
log.debug("Generating marshaller for {}", fullyQualifiedTypeName);
final String generatedSource = generateMarshaller(context, type, className, marshallerTypeName, logger, printWriter);
cachedPortableTypes.put(fullyQualifiedTypeName, type);
cachedSourceByTypeName.put(fullyQualifiedTypeName, generatedSource);
}
return new RebindResult(RebindMode.USE_ALL_NEW, marshallerTypeName);
} else {
log.debug("Reusing existing marshaller for {}", fullyQualifiedTypeName);
return new RebindResult(RebindMode.USE_EXISTING, marshallerTypeName);
}
}
private String generateMarshaller(final GeneratorContext context, final MetaClass type, final String className,
final String marshallerTypeName, final TreeLogger logger, final PrintWriter printWriter) {
final MarshallerOutputTarget target = MarshallerOutputTarget.GWT;
final MappingStrategy strategy =
MappingStrategyFactory.createStrategy(true, GeneratorMappingContextFactory.getFor(context, target), type);
String gen = null;
if (type.isArray()) {
final BuildMetaClass marshallerClass =
MarshallerGeneratorFactory.generateArrayMarshaller(type, marshallerTypeName, true);
gen = marshallerClass.toJavaString();
}
else {
final ClassStructureBuilder<?> marshaller = strategy.getMapper().getMarshaller(marshallerTypeName);
gen = marshaller.toJavaString();
}
printWriter.append(gen);
RebindUtils.writeStringToJavaSourceFileInErraiCacheDir(packageName, className, gen);
context.commit(logger, printWriter);
return gen;
}
private String distillTargetTypeName(final String marshallerName) {
final int pos = marshallerName.lastIndexOf(MarshallerGeneratorFactory.MARSHALLER_NAME_PREFIX);
String typeName = marshallerName.substring(pos).replace(MarshallerGeneratorFactory.MARSHALLER_NAME_PREFIX, "");
final boolean isArrayType = typeName.startsWith(MarshallingGenUtil.ARRAY_VAR_PREFIX);
typeName = StringUtils.replace(typeName, MarshallingGenUtil.ARRAY_VAR_PREFIX, "");
typeName = StringUtils.replace(typeName, "_", ".");
typeName = StringUtils.replace(typeName, MarshallingGenUtil.ERRAI_DOLLARSIGN_REPLACEMENT, "$");
typeName = StringUtils.replace(typeName, MarshallingGenUtil.ERRAI_UNDERSCORE_REPLACEMENT, "_");
if (isArrayType) {
final int lastDot = typeName.lastIndexOf(".");
final int dimension = Integer.parseInt(typeName.substring(lastDot + 2));
typeName = typeName.substring(0, lastDot);
final String primitiveName = AbstractMetaClass.getInternalPrimitiveNameFrom(typeName);
final boolean isPrimitiveArrayType = !primitiveName.equals(typeName);
typeName = "";
for (int i = 0; i < dimension; i++) {
typeName += "[";
}
if (!isPrimitiveArrayType) {
typeName += "L";
}
typeName += primitiveName;
if (!isPrimitiveArrayType) {
typeName += ";";
}
}
return typeName;
}
@Override
public long getVersionId() {
return GENERATOR_VERSION_ID;
}
}