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
   *
   *     http://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.ambari.server.collections.functors;
  
  import java.util.Map;
  
  import org.apache.commons.collections4.Transformer;
  
  /**
   * {@link ContextTransformer} is a {@link Transformer} implementation that traverses a {@link Map}
   * of {@link Map}s to find the value related to the specified key.
   * <p>
   * This implementation first checks the context map for the explicit key. If the key exists, it
   * returns the value associated with it. If the key does not exist and appears to represent a path
   * via "/"-delimited keys (configurations/service-site/property_name), the path is traversed
   * recursively looking for the requested data.
   * <p>
   * Example context:
   * <pre>
   * |- services : [set of services]
   * |
   * |- configurations
   * |  |- service-site
   * |  |  |- property : [value]
   * |
   * |- key/looks/like/path : [value2]
   * </pre>
   * <ul>
   * <li>If the key was <code>services</code>, <code>[set of services]</code> would be returned</li>
   * <li>If the key was <code>configurations/service-site/property</code>, <code>value</code> would be returned</li>
   * <li>If the key was <code>configurations/service-site</code>, <code>[map of service-site properties]</code> would be returned</li>
   * <li>If the key was <code>key/looks/like/path</code>, <code>value2</code> would be returned</li>
   * </ul>
   */
  public class ContextTransformer implements Transformer {
  
    /**
     * The key to search for
     */
    private final String key;
  
    /**
     * Constructor.
     *
     * @param key the key to search for
     */
    public ContextTransformer(String key) {
      this.key = key;
    }
  
    /**
     * Returns the key this transformer is using to search for data
     *
     * @return a string
     */
    public String getKey() {
      return key;
    }
  
    @Override
    public Object transform(Object o) {
      return transform(key, o);
    }
  
    /**
     * Traverses the input object (expected to be a {@link Map}) to find the data associated with
     * the specified key.
     * <p>
     * Note: This method is recursive in the even the key represents a path
     *
     * @param key the key to search for
     * @param o   the object containing data to process
     * @return the found data or null
     */
    private Object transform(String key, Object o) {
      Object transformedData = null;
  
      if (key != null) {
        if (o instanceof Map) {
          Map<?, ?> data = (Map) o;
  
          if (data.containsKey(key)) {
            transformedData = data.get(key);
          } else {
            // See if the key implies a tree that needs to be traversed...
            // For example:  configurations/service-conf/property_name
            //  A map of maps is expected such that the top level map has a map identified by the key
            //  of "configuration".  The "configuration" map has a map identified by the key of
            // "service-conf". The "service-conf" has a value identified by the key of "property_name".
            String[] parts = key.split("\\/", 2);
  
            // If only a single item is returned, than the key does not indicate a tree.
            if (parts.length == 2) {
              // If the first item is empty, a leading "/" was encountered... retry with the pruned key
              if (parts[0].isEmpty()) {
                transformedData = transform(parts[1], o);
              } else {
                transformedData = transform(parts[1], data.get(parts[0]));
              }
            }
          }
        }
      }
  
      return transformedData;
    }
  
    @Override
    public int hashCode() {
      return (37 * ((key == null) ? 0 : key.hashCode()));
    }
  
    @Override
    public boolean equals(Object obj) {
      if (obj == this) {
        return true;
      } else if (obj == null) {
        return false;
      } else if ((obj instanceof ContextTransformer) && (hashCode() == obj.hashCode())) {
        ContextTransformer t = (ContextTransformer) obj;
        return (key == null) ? (t.key == null) : key.equals(t.key);
      } else {
        return false;
      }
    }
  }
  