All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.dev.hickory.incremental.StateSaver Maven / Gradle / Ivy

The newest version!
/*
 * StateSaver.java
 * 
 * Created on 8/09/2007, 20:30:54
 * 
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.java.dev.hickory.incremental;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;

/**
 *
 *  When a single generated file is generated from more than one compilation unit
 * then normally a processor is not safe for use in incremental compilations.
 * This class saves state between rounds and between runs of a processor allowing
 * generation to occur even if some of the contributing data was obtained in previous
 * runs.
 * 

* Imposes a strict ordering call sequence to enforce correct use and remove the need * for the caller to explicitly restore or save the persisted state. *

* Use of this class is illustrated by the following example which generates provider-configuration files *for use with {@link java.util.ServiceLoader} using an annotation on the Service Provider *which specifies the Service. For simplicity some checks are ommitted, and a Prism is used *to obtain the provider from the annotation. *

 *{@literal
   }{@literal @Target(ElementType.TYPE)
   }{@literal @Retention(RetentionPolicy.SOURCE)
public @interface ServiceProvider {
    Class value();
}

    }{@literal @SupportedAnnotationTypes("ServiceProvider")
    }{@literal @SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ServiceProviderProcessor extends AbstractProcessor {
    
    public ServiceProviderProcessor() {}

    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        
        if(roundEnv.processingOver() && ! roundEnv.errorRaised()) {
            generate();
        } else if(! roundEnv.errorRaised()) {
            StateSaver stateSaver = StateSaver.getInstance(this, State.class, processingEnv);
            stateSaver.startRound(roundEnv);
            TypeElement serviceProviderElement = processingEnv.getElementUtils().getTypeElement(ServiceProvider.class.getCanonicalName());
            for(Element target : roundEnv.getElementsAnnotatedWith(serviceProviderElement)) {
                TypeElement targetType = (TypeElement)target;
                ServiceProviderPrism prism = ServiceProviderPrism.getInstanceOn(target);
                if(prism.isValid) {
                    DeclaredType service = (DeclaredType)prism.value();
                    // SHOULD check validity here. service is supertype of target, target has public no args constructor etc
                    State state = new State(((TypeElement)service.asElement()).getQualifiedName().toString(),
                            targetType.getQualifiedName().toString());
                    stateSaver.addData(targetType,state);
                }
            }
        }
        return true;
    }

    private void generate() {
        StateSaver stateSaver = StateSaver.getInstance(this, State.class, processingEnv);
        Comparator byService = new Comparator() {
            public int compare(ServiceProviderProcessor.State o1, ServiceProviderProcessor.State o2) {
                return o1.service.compareTo(o2.service);
            }
        };
        Filer filer = processingEnv.getFiler();
        for(List providers : stateSaver.getData(byService)) {
            State example = providers.get(0);
            try {
                FileObject f = filer.createResource(StandardLocation.CLASS_OUTPUT,"","META-INF/services/" + example.service);
                Writer out = f.openWriter();
                for(State provider : providers) {
                    out.write(provider.provider);
                    out.write("\n");
                }
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
                throw new RuntimeException(ex);
            }
        }
    }
        
    static class State implements Serializable {
        String service;
        String provider;
        State(String service, String provider) {
            this.service = service;
            this.provider = provider;
        }
    }
}

 * }
 * 
* @param The type of the data that will be saved in this StateSaver. * @author Bruce */ public class StateSaver { static Map> cache = new HashMap>(); Filer filer; static ProcessingEnvironment penv; private boolean stateSaved; private boolean stateRoundStarted; String persistenceName; private StateSaver(String processorFQN, Filer filer) { // TODO deserialize data if available persistenceName = processorFQN; this.filer = filer; restore(); } /** Return an instance for use by the specified processor. * @param processor The processor that needs this instance. Is used to * determine the name of the file in which state is persisted. Consequently each * Processor can have only one StateSaver * @param type The Class of the data being managed by this StateSaver * @param penv The current ProcessingEnvironment from which teh Filer can be obtained. * @param The type of the data being managed by this StateSaver * */ public static StateSaver getInstance(Processor processor, Class type, ProcessingEnvironment penv) { // get from cache or make new and read serialized data from previous runs if(penv != StateSaver.penv) { // new run - save the filer and penv, and reset the cache StateSaver.penv = penv; cache.clear(); } String key = processor.getClass().getName() + " " + type.getClass().getName(); if(cache.containsKey(key)) { return (StateSaver) cache.get(key); } else { StateSaver result = new StateSaver(key,penv.getFiler()); cache.put(key,result); return result; } } private Map> srcCompUnitData; /** This method must be called at the start of each round. It removes any remembered data * for the compilation units in this round, so that calls to addData() during the round * will replace any previous data for those compilation units. * @param env The current round Environment. * @throws IllegalStateException if this is called after either of the getdata() methods * (since calling either getData() method implies that collecting new data for the run * is complete and the current collected state should be persisted). */ public void startRound(RoundEnvironment env) { stateRoundStarted = true; if(stateSaved) throw new IllegalStateException("getData() has already been called in this processor run"); for(Element ele : env.getRootElements()) { if(ele.getKind() == ElementKind.CLASS || ele.getKind() == ElementKind.INTERFACE) { TypeElement cu = (TypeElement)ele; String fqn = cu.getQualifiedName().toString(); srcCompUnitData.remove(fqn); } } } /** Save some state obtained from the specified TypeElement. * @param contributor The TypeElement from which the data was obtained * @param data the Data to save. * @throws IllegalStateException if this is called after either of the getdata() methods * (since calling either getData() method implies that collecting new data for the run * is complete and the current collected state should be persisted). * @throws IllegalStateException if startRound() has not been called. */ public void addData(TypeElement contributor, T data) { if(! stateRoundStarted) { throw new IllegalStateException("startRound() must be called before addData()"); } String ownerFQN = contributor.getQualifiedName().toString(); List existing = srcCompUnitData.get(ownerFQN); if(existing == null) { existing = new ArrayList(); srcCompUnitData.put(ownerFQN,existing); } existing.add(data); } /** Return all the data that has been saved, during all runs * including the current one. * */ public List getData() { if(! stateSaved) { save(); stateSaved = true; } List result = new ArrayList(); for(List sub : srcCompUnitData.values() ) { result.addAll(sub); } return result; } /** Return all the data, grouped into lists of items which the * comparator considers equal. If you supply a Comparator which * compares based on the FQN of the file (.java or .class) to be generated * then you can generate one file from each returned list. */ public Iterable> getData(Comparator comparator) { List flat = getData(); Collections.sort(flat,comparator); List> result = new ArrayList>(); T prev = null; List current = null; for(T t : flat) { if(prev == null || comparator.compare(t, prev) != 0) { // start a new current list current = new ArrayList(); result.add(current); } current.add(t); prev = t; } return result; } private void save() { try { FileObject fo = filer.createResource(StandardLocation.SOURCE_OUTPUT, StateSaver.class.getName(), persistenceName + ".state"); ObjectOutputStream oos = new ObjectOutputStream(fo.openOutputStream()); oos.writeObject(srcCompUnitData); oos.close(); } catch (IOException ex) { ex.printStackTrace(); } } private void restore() { FileObject fo = null; try { fo = filer.getResource(StandardLocation.SOURCE_OUTPUT, StateSaver.class.getName(), persistenceName + ".state"); // System.out.format("stateSaver - restoring %s %s %s%n",fo.getClass(),fo.getName(),fo.toUri()); ObjectInputStream ois = new ObjectInputStream(fo.openInputStream()); srcCompUnitData = (Map>)ois.readObject(); ois.close(); } catch (FileNotFoundException fnf) { // thats OK, maybe this is first run after a clean srcCompUnitData = new HashMap>(); } catch (IOException ex) { System.err.format("reading: %s%n",fo.toUri()); ex.printStackTrace(); } catch (ClassNotFoundException ex) { System.err.format("reading: %s%n",fo.toUri()); ex.printStackTrace(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy