content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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
   *
   *     https://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.apache.avro.compiler.schema;
  
  import java.util.ArrayList;
  import java.util.IdentityHashMap;
  import java.util.List;
  
  import org.apache.avro.Schema;
  
  import static org.apache.avro.Schema.Type.RECORD;
  
  /**
   * this visitor will create a clone of the original Schema with docs and other
   * nonessential fields stripped by default. what attributes are copied is
   * customizable.
   */
  public final class CloningVisitor implements SchemaVisitor<Schema> {
  
    private final IdentityHashMap<Schema, Schema> replace = new IdentityHashMap<>();
  
    private final Schema root;
  
    private final PropertyCopier copyProperties;
  
    private final boolean copyDocs;
  
    public interface PropertyCopier {
      void copy(Schema first, Schema second);
  
      void copy(Schema.Field first, Schema.Field second);
    }
  
    /**
     * copy only serialization necessary fields.
     *
     * @param root
     */
    public CloningVisitor(final Schema root) {
      this(new PropertyCopier() {
        @Override
        public void copy(final Schema first, final Schema second) {
          Schemas.copyLogicalTypes(first, second);
          Schemas.copyAliases(first, second);
        }
  
        @Override
        public void copy(final Schema.Field first, final Schema.Field second) {
          Schemas.copyAliases(first, second);
        }
      }, false, root);
    }
  
    public CloningVisitor(final PropertyCopier copyProperties, final boolean copyDocs, final Schema root) {
      this.copyProperties = copyProperties;
      this.copyDocs = copyDocs;
      this.root = root;
    }
  
    @Override
    public SchemaVisitorAction visitTerminal(final Schema terminal) {
      Schema.Type type = terminal.getType();
      Schema newSchema;
      switch (type) {
      case RECORD: // recursion.
      case ARRAY:
      case MAP:
      case UNION:
        if (!replace.containsKey(terminal)) {
          throw new IllegalStateException("Schema " + terminal + " must be already processed");
        }
        return SchemaVisitorAction.CONTINUE;
      case BOOLEAN:
      case BYTES:
      case DOUBLE:
      case FLOAT:
      case INT:
      case LONG:
      case NULL:
      case STRING:
        newSchema = Schema.create(type);
        break;
      case ENUM:
        newSchema = Schema.createEnum(terminal.getName(), copyDocs ? terminal.getDoc() : null, terminal.getNamespace(),
            terminal.getEnumSymbols());
        break;
      case FIXED:
        newSchema = Schema.createFixed(terminal.getName(), copyDocs ? terminal.getDoc() : null, terminal.getNamespace(),
            terminal.getFixedSize());
        break;
      default:
        throw new IllegalStateException("Unsupported schema " + terminal);
      }
      copyProperties.copy(terminal, newSchema);
      replace.put(terminal, newSchema);
      return SchemaVisitorAction.CONTINUE;
    }
  
    @Override
    public SchemaVisitorAction visitNonTerminal(final Schema nt) {
      Schema.Type type = nt.getType();
      if (type == RECORD) {
        Schema newSchema = Schema.createRecord(nt.getName(), copyDocs ? nt.getDoc() : null, nt.getNamespace(),
            nt.isError());
        copyProperties.copy(nt, newSchema);
        replace.put(nt, newSchema);
      }
      return SchemaVisitorAction.CONTINUE;
    }
  
    @Override
    public SchemaVisitorAction afterVisitNonTerminal(final Schema nt) {
      Schema.Type type = nt.getType();
      Schema newSchema;
      switch (type) {
      case RECORD:
        newSchema = replace.get(nt);
        List<Schema.Field> fields = nt.getFields();
        List<Schema.Field> newFields = new ArrayList<>(fields.size());
        for (Schema.Field field : fields) {
          Schema.Field newField = new Schema.Field(field.name(), replace.get(field.schema()),
              copyDocs ? field.doc() : null, field.defaultVal(), field.order());
          copyProperties.copy(field, newField);
          newFields.add(newField);
        }
        newSchema.setFields(newFields);
        return SchemaVisitorAction.CONTINUE;
      case UNION:
        List<Schema> types = nt.getTypes();
        List<Schema> newTypes = new ArrayList<>(types.size());
        for (Schema sch : types) {
          newTypes.add(replace.get(sch));
        }
        newSchema = Schema.createUnion(newTypes);
        break;
      case ARRAY:
        newSchema = Schema.createArray(replace.get(nt.getElementType()));
        break;
      case MAP:
        newSchema = Schema.createMap(replace.get(nt.getValueType()));
        break;
      default:
        throw new IllegalStateException("Illegal type " + type + ", schema " + nt);
      }
      copyProperties.copy(nt, newSchema);
      replace.put(nt, newSchema);
      return SchemaVisitorAction.CONTINUE;
    }
  
    @Override
    public Schema get() {
      return replace.get(root);
    }
  
    @Override
    public String toString() {
      return "CloningVisitor{" + "replace=" + replace + ", root=" + root + '}';
    }
  
  }
  