1/*
2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javac.code;
27
28import com.sun.tools.javac.util.Assert;
29import com.sun.tools.javac.util.List;
30import java.util.EnumMap;
31import java.util.HashSet;
32import java.util.Set;
33
34/**
35 * TypeMetadata is essentially an immutable {@code EnumMap<Entry.Kind, <? extends Entry>>}
36 *
37 * A metadata class represented by a subtype of Entry can express a property on a Type instance.
38 * Thers should be at most one instance of an Entry per Entry.Kind on any given Type instance.
39 *
40 * Metadata classes of a specific kind are responsible for how they combine themselvs.
41 *
42 * @implNote {@code Entry:combine} need not be commutative.
43 */
44public class TypeMetadata {
45    public static final TypeMetadata EMPTY = new TypeMetadata();
46
47    private final EnumMap<Entry.Kind, Entry> contents;
48
49    /**
50     * Create a new empty TypeMetadata map.
51     */
52    private TypeMetadata() {
53        contents = new EnumMap<>(Entry.Kind.class);
54    }
55
56    /**
57     * Create a new TypeMetadata map containing the Entry {@code elem}.
58     *
59     * @param elem the sole contents of this map
60     */
61    public TypeMetadata(Entry elem) {
62        this();
63        Assert.checkNonNull(elem);
64        contents.put(elem.kind(), elem);
65    }
66
67    /**
68     * Creates a copy of TypeMetadata {@code other} with a shallow copy the other's metadata contents.
69     *
70     * @param other the TypeMetadata to copy contents from.
71     */
72    public TypeMetadata(TypeMetadata other) {
73        Assert.checkNonNull(other);
74        contents = other.contents.clone();
75    }
76
77    /**
78     * Return a copy of this TypeMetadata with the metadata entry for {@code elem.kind()} combined
79     * with {@code elem}.
80     *
81     * @param elem the new value
82     * @return a new TypeMetadata updated with {@code Entry elem}
83     */
84    public TypeMetadata combine(Entry elem) {
85        Assert.checkNonNull(elem);
86
87        TypeMetadata out = new TypeMetadata(this);
88        Entry.Kind key = elem.kind();
89        if (contents.containsKey(key)) {
90            out.add(key, this.contents.get(key).combine(elem));
91        } else {
92            out.add(key, elem);
93        }
94        return out;
95    }
96
97    /**
98     * Return a copy of this TypeMetadata with the metadata entry for all kinds from {@code other}
99     * combined with the same kind from this.
100     *
101     * @param other the TypeMetadata to combine with this
102     * @return a new TypeMetadata updated with all entries from {@code other}
103     */
104    public TypeMetadata combineAll(TypeMetadata other) {
105        Assert.checkNonNull(other);
106
107        TypeMetadata out = new TypeMetadata();
108        Set<Entry.Kind> keys = new HashSet<>(contents.keySet());
109        keys.addAll(other.contents.keySet());
110
111        for(Entry.Kind key : keys) {
112            if (contents.containsKey(key)) {
113                if (other.contents.containsKey(key)) {
114                    out.add(key, contents.get(key).combine(other.contents.get(key)));
115                } else {
116                    out.add(key, contents.get(key));
117                }
118            } else if (other.contents.containsKey(key)) {
119                out.add(key, other.contents.get(key));
120            }
121        }
122        return out;
123    }
124
125    /**
126     * Return a TypeMetadata with the metadata entry for {@code kind} removed.
127     *
128     * This may be the same instance or a new TypeMetadata.
129     *
130     * @param kind the {@code Kind} to remove metadata for
131     * @return a new TypeMetadata without {@code Kind kind}
132     */
133    public TypeMetadata without(Entry.Kind kind) {
134        if (this == EMPTY || contents.get(kind) == null)
135            return this;
136
137        TypeMetadata out = new TypeMetadata(this);
138        out.contents.remove(kind);
139        return out.contents.isEmpty() ? EMPTY : out;
140    }
141
142    public Entry get(Entry.Kind kind) {
143        return contents.get(kind);
144    }
145
146    private void add(Entry.Kind kind, Entry elem) {
147        contents.put(kind, elem);
148    }
149
150    public interface Entry {
151
152        public enum Kind {
153            ANNOTATIONS
154        }
155
156        /**
157         * Get the kind of metadata this object represents
158         */
159        public Kind kind();
160
161        /**
162         * Combine this type metadata with another metadata of the
163         * same kind.
164         *
165         * @param other The metadata with which to combine this one.
166         * @return The combined metadata.
167         */
168        public Entry combine(Entry other);
169    }
170
171    /**
172     * A type metadata object holding type annotations.
173     */
174    public static class Annotations implements Entry {
175        private List<Attribute.TypeCompound> annos;
176
177        public static final List<Attribute.TypeCompound> TO_BE_SET = List.nil();
178
179        public Annotations(List<Attribute.TypeCompound> annos) {
180            this.annos = annos;
181        }
182
183        /**
184         * Get the type annotations contained in this metadata.
185         *
186         * @return The annotations.
187         */
188        public List<Attribute.TypeCompound> getAnnotations() {
189            return annos;
190        }
191
192        @Override
193        public Annotations combine(Entry other) {
194            Assert.check(annos == TO_BE_SET);
195            annos = ((Annotations)other).annos;
196            return this;
197        }
198
199        @Override
200        public Kind kind() { return Kind.ANNOTATIONS; }
201
202        @Override
203        public String toString() { return "ANNOTATIONS [ " + annos + " ]"; }
204    }
205}
206