001/*
002 * Copyright (C) 2012 Facebook, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may
005 * not use this file except in compliance with the License. You may obtain
006 * a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations
014 * under the License.
015 */
016package com.facebook.swift.codec.metadata;
017
018import com.facebook.swift.codec.ThriftField;
019import com.google.common.base.Function;
020import com.google.common.base.Optional;
021import com.google.common.base.Predicate;
022import com.google.common.collect.ImmutableList;
023
024import javax.annotation.concurrent.Immutable;
025
026import java.util.List;
027import java.util.Objects;
028
029import static com.facebook.swift.codec.ThriftField.Requiredness;
030import static com.google.common.base.Preconditions.checkArgument;
031import static com.google.common.base.Preconditions.checkNotNull;
032
033/**
034 * ThriftFieldMetadata defines a single thrift field including the value extraction and injection
035 * points.
036 */
037@Immutable
038public class ThriftFieldMetadata
039{
040    private final short id;
041    private final ThriftType thriftType;
042    private final String name;
043    private final FieldKind fieldKind;
044    private final List<ThriftInjection> injections;
045    private final Optional<ThriftConstructorInjection> constructorInjection;
046    private final Optional<ThriftMethodInjection> methodInjection;
047    private final Optional<ThriftExtraction> extraction;
048    private final Optional<TypeCoercion> coercion;
049    private final ImmutableList<String> documentation;
050    private final Requiredness requiredness;
051
052    public ThriftFieldMetadata(
053            short id,
054            Requiredness requiredness,
055            ThriftType thriftType,
056            String name,
057            FieldKind fieldKind,
058            List<ThriftInjection> injections,
059            Optional<ThriftConstructorInjection> constructorInjection,
060            Optional<ThriftMethodInjection> methodInjection,
061            Optional<ThriftExtraction> extraction,
062            Optional<TypeCoercion> coercion
063    )
064    {
065        this.requiredness = requiredness;
066        this.thriftType= checkNotNull(thriftType, "thriftType is null");
067        this.fieldKind = checkNotNull(fieldKind, "type is null");
068        this.name = checkNotNull(name, "name is null");
069        this.injections = ImmutableList.copyOf(checkNotNull(injections, "injections is null"));
070        this.constructorInjection = checkNotNull(constructorInjection, "constructorInjection is null");
071        this.methodInjection = checkNotNull(methodInjection, "methodInjection is null");
072
073        this.extraction = checkNotNull(extraction, "extraction is null");
074        this.coercion = checkNotNull(coercion, "coercion is null");
075
076        switch (fieldKind) {
077            case THRIFT_FIELD:
078                checkArgument(id >= 0, "id is negative");
079                break;
080            case THRIFT_UNION_ID:
081                checkArgument(id == Short.MIN_VALUE, "thrift union id must be Short.MIN_VALUE");
082                break;
083        }
084
085        checkArgument(!injections.isEmpty()
086                      || extraction.isPresent()
087                      || constructorInjection.isPresent()
088                      || methodInjection.isPresent(), "A thrift field must have an injection or extraction point");
089
090        this.id = id;
091
092        if (extraction.isPresent()) {
093            if (extraction.get() instanceof ThriftFieldExtractor) {
094                ThriftFieldExtractor e = (ThriftFieldExtractor)extraction.get();
095                this.documentation = ThriftCatalog.getThriftDocumentation(e.getField());
096            } else if (extraction.get() instanceof ThriftMethodExtractor) {
097                ThriftMethodExtractor e = (ThriftMethodExtractor)extraction.get();
098                this.documentation = ThriftCatalog.getThriftDocumentation(e.getMethod());
099            }
100            else {
101                this.documentation = ImmutableList.of();
102            }
103        } else {
104            // no extraction = no documentation
105            this.documentation = ImmutableList.of();
106        }
107    }
108
109    public short getId()
110    {
111        return id;
112    }
113
114    public ThriftType getThriftType()
115    {
116        return thriftType;
117    }
118
119    public Requiredness getRequiredness() { return requiredness; }
120
121    public String getName()
122    {
123        return name;
124    }
125
126    public FieldKind getType()
127    {
128        return fieldKind;
129    }
130
131    public boolean isInternal()
132    {
133        switch (getType()) {
134            // These are normal thrift fields (i.e. they should be emitted by the swift2thrift generator)
135            case THRIFT_FIELD:
136                return false;
137
138            // Other fields types are used internally in swift, but do not make up part of the external
139            // thrift interface
140            default:
141                return true;
142        }
143    }
144
145    public boolean isReadOnly()
146    {
147        return injections.isEmpty() && !constructorInjection.isPresent() && !methodInjection.isPresent();
148    }
149
150    public boolean isWriteOnly()
151    {
152        return !extraction.isPresent();
153    }
154
155    public List<ThriftInjection> getInjections()
156    {
157        return injections;
158    }
159
160    public Optional<ThriftConstructorInjection> getConstructorInjection()
161    {
162        return constructorInjection;
163    }
164
165    public Optional<ThriftMethodInjection> getMethodInjection()
166    {
167        return methodInjection;
168    }
169
170    public Optional<ThriftExtraction> getExtraction()
171    {
172        return extraction;
173    }
174
175    public Optional<TypeCoercion> getCoercion()
176    {
177        return coercion;
178    }
179
180    public ImmutableList<String> getDocumentation()
181    {
182        return documentation;
183    }
184
185    @Override
186    public String toString()
187    {
188        final StringBuilder sb = new StringBuilder();
189        sb.append("ThriftFieldMetadata");
190        sb.append("{id=").append(id);
191        sb.append(", thriftType=").append(thriftType);
192        sb.append(", name='").append(name).append('\'');
193        sb.append(", fieldKind=").append(fieldKind);
194        sb.append(", injections=").append(injections);
195        sb.append(", constructorInjection=").append(constructorInjection);
196        sb.append(", methodInjection=").append(methodInjection);
197        sb.append(", extraction=").append(extraction);
198        sb.append(", coercion=").append(coercion);
199        sb.append('}');
200        return sb.toString();
201    }
202
203    @Override
204    public int hashCode()
205    {
206        return Objects.hash(id, thriftType, name);
207    }
208
209    @Override
210    public boolean equals(Object obj)
211    {
212        if (this == obj) {
213            return true;
214        }
215        if (obj == null || getClass() != obj.getClass()) {
216            return false;
217        }
218        final ThriftFieldMetadata other = (ThriftFieldMetadata) obj;
219        return Objects.equals(this.id, other.id) && Objects.equals(this.thriftType, other.thriftType) && Objects.equals(this.name, other.name);
220    }
221
222    public static Function<ThriftFieldMetadata, Short> getIdGetter()
223    {
224        return new Function<ThriftFieldMetadata, Short>() {
225            @Override
226            public Short apply(ThriftFieldMetadata metadata)
227            {
228                return metadata.getId();
229            }
230        };
231    }
232
233    public static Predicate<ThriftFieldMetadata> isTypePredicate(final FieldKind type)
234    {
235        return new Predicate<ThriftFieldMetadata>() {
236            @Override
237            public boolean apply(ThriftFieldMetadata fieldMetadata)
238            {
239                return fieldMetadata.getType() == type;
240            }
241        };
242    }
243}