aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/gnunet/construct/MessageIdAnnotationProcessor.java
blob: 9f2e4a9f46d0291113a55600d0ae644c0e422080 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
 This file is part of GNUnet.
 (C) 2011, 2012 Christian Grothoff (and other contributing authors)

 GNUnet is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published
 by the Free Software Foundation; either version 3, or (at your
 option) any later version.

 GNUnet is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with GNUnet; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA 02111-1307, USA.
 */

package org.gnunet.construct;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;


/**
 * Creates a resource file 'MsgMap.txt' in the package 'org.gnunet.construct' of the source tree.
 */
@SupportedAnnotationTypes("org.gnunet.construct.UnionCase")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MessageIdAnnotationProcessor extends AbstractProcessor {
    private final Table<String, Integer, String> idToMember = HashBasedTable.create();

    @Override
    public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {
        if (roundEnvironment.errorRaised()) {
            return false;
        }

        Types types = processingEnv.getTypeUtils();
        Elements elements = processingEnv.getElementUtils();

        if (roundEnvironment.processingOver()) {
            Filer filer = processingEnv.getFiler();
            FileObject outfile;
            try {
                outfile = filer.createResource(StandardLocation.SOURCE_OUTPUT, "org.gnunet.construct", "MsgMap.txt");
            } catch (IOException e) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not create MsgMap.txt");
                return false;
            }

            try {
                Writer w = outfile.openWriter();
                for (Table.Cell<String, Integer, String> cell : idToMember.cellSet()) {
                    w.write(cell.getRowKey() + '|' + cell.getColumnKey() + '=' + cell.getValue() + '\n');
                }

                DateFormat fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

                w.write("# generated " + fmt.format(new Date()) + '\n');
                w.close();
            } catch (IOException e) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not write MsgMap.txt");
            }

            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "message map written to " + outfile.toUri());

        } else {
            for (Element e : roundEnvironment.getElementsAnnotatedWith(UnionCase.class)) {
                UnionCase ann = e.getAnnotation(UnionCase.class);
                // get the uppermost parent class that implements MessageUnion. This is the union type.
                // processingEnv.getElementUtils().
                //processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "element :" + e.toString());
                List<? extends TypeMirror> parents = processingEnv.getTypeUtils().directSupertypes(e.asType());
                TypeMirror msg = elements.getTypeElement("org.gnunet.construct.MessageUnion").asType();
                TypeMirror unionInterface = null;
                for (TypeMirror p : parents) {
                    if (types.isSubtype(p, msg)) {
                        unionInterface = p;
                        break;
                    }
                }
                if (unionInterface == null) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(
                            "class %s annotated with @UnionCase does not implement an interface inheriting MessageUnion", e.getSimpleName()));
                    return false;
                }
                String unionName = getClassName(types.asElement(unionInterface));
                if (idToMember.contains(unionName, ann.value())) {
                    String existingName = idToMember.get(unionName, ann.value());
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(
                            "duplicate message id %s for union '%s: '%s' and '%s'", ann.value(), unionName,
                            getClassName(e), existingName));
                    return false;
                }
                idToMember.put(unionName, ann.value(), getClassName(e));
            }
        }

        return false;
    }

    /**
     * Get the fully qualified class name, where packages are seperated with '.', and
     * inner classes are separated with '$'
     *
     * @param e the Element representing a class
     * @return the fully qualified class name
     */
    private String getClassName(Element e) {

        assert e.getKind().isClass();

        String name = e.getSimpleName().toString();
        String pkg = processingEnv.getElementUtils().getPackageOf(e).getQualifiedName().toString() + '.';

        String outer = "";

        while (((e = e.getEnclosingElement()) != null) && e.getKind().isClass()) {
            outer = String.format("%s$%s", e.getSimpleName(), outer);
        }

        return pkg + outer + name;
    }
}