code: /**
   * This file is part of Eclipse Steady.
   *
   * Licensed 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.
   *
   * SPDX-License-Identifier: Apache-2.0
   * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
   */
  package org.eclipse.steady.java.sign;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.Modifier;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.HashSet;
  import java.util.Set;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import org.apache.logging.log4j.Logger;
  import org.eclipse.steady.ConstructId;
  import org.eclipse.steady.core.util.SignatureConfiguration;
  import org.eclipse.steady.shared.util.VulasConfiguration;
  
  import ch.uzh.ifi.seal.changedistiller.model.classifiers.EntityType;
  import ch.uzh.ifi.seal.changedistiller.model.entities.IUniqueNameNormalizer;
  import ch.uzh.ifi.seal.changedistiller.model.entities.SourceCodeEntity;
  
  /**
   * <p>UniqueNameNormalizer class.</p>
   */
  public class UniqueNameNormalizer implements IUniqueNameNormalizer {
  
    private static final Logger log =
        org.apache.logging.log4j.LogManager.getLogger(UniqueNameNormalizer.class);
  
    private static final Pattern CONSTANT_PATTERN =
        Pattern.compile("([0-9a-zA-Z_\\.]+)\\.([0-9A-Z_]+)");
  
    private static UniqueNameNormalizer instance = null;
  
    private ClassLoader classLoader = null;
  
    private static String cua = null; // class under analysis
  
    private UniqueNameNormalizer() {}
  
    /**
     * <p>Getter for the field <code>instance</code>.</p>
     *
     * @return a {@link org.eclipse.steady.java.sign.UniqueNameNormalizer} object.
     */
    public static synchronized UniqueNameNormalizer getInstance() {
      if (instance == null) instance = new UniqueNameNormalizer();
      return instance;
    }
  
    /**
     * Fully qualified class names to be searched for class names found in strings.
     */
    private final Set<String> classNames = new HashSet<String>();
    /**
     * <p>addStrings.</p>
     *
     * @param _class_names a {@link java.util.Collection} object.
     */
    public final void addStrings(Collection<String> _class_names) {
      classNames.addAll(_class_names);
    }
    /**
     * <p>addStrings.</p>
     *
     * @param _class_names an array of {@link java.lang.String} objects.
     */
    public final void addStrings(String[] _class_names) {
      classNames.addAll(Arrays.asList(_class_names));
    }
    /**
     * <p>addConstructIds.</p>
     *
     * @param _classes a {@link java.util.Collection} object.
     */
    public final void addConstructIds(Collection<ConstructId> _classes) {
      for (ConstructId cid : _classes) {
        classNames.add(cid.getQualifiedName());
      }
    }
  
    /**
     * Sets the class loader used for inlining constants, see {@link UniqueNameNormalizer#findConstants(String, String)}.
     *
     * @param _cl a {@link java.lang.ClassLoader} object.
     */
    public void setClassLoader(ClassLoader _cl) {
      this.classLoader = _cl;
    }
  
    /** {@inheritDoc} */
    @Override
    public boolean haveEqualUniqueName(SourceCodeEntity _e1, SourceCodeEntity _e2) {
      // Equality before normalization
      final boolean eq_before_norm = _e1.getUniqueName().equals(_e2.getUniqueName());
  
      // Equality after normalization
      final String p1 = this.normalizeUniqueName(_e1);
      final String p2 = this.normalizeUniqueName(_e2);
      final boolean eq_after_norm = p1.equals(p2);
  
      // Print log message in case the pre-processing realized a match that did not exist before
      if (!eq_before_norm && eq_after_norm) { // && !_e1.equals(_e2)) {
        UniqueNameNormalizer.log.info("Preprocessor match: Old [" + _e1 + "] and [" + _e2 + "]");
        UniqueNameNormalizer.log.info("                    New [" + p1 + "] and [" + p2 + "]");
      }
      return eq_after_norm;
    }
  
    /**
     * Applies changes that are independent of a given {@link EntityType} to the given {@link String}.
     * In more detail, the string is trimmed, occurrences of "this." are removed and constants are inlined.
     *
     * @param _string a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String normalizeUniqueName(String _string) {
      String tmp = _string.trim();
      tmp = tmp.replaceAll("this\\.", "");
      tmp = tmp.replaceAll("(Object)", "");
      if (VulasConfiguration.getGlobal()
          .getConfiguration()
          .getBoolean(SignatureConfiguration.RELAX_STRIP_FINALS)) {
        tmp = tmp.replaceAll("final ", "");
      }
      // TODO: Replace single characters 'x' by ASCII codes (cf. CVE-2009-2625) as done by some
      // compilers
      tmp = this.inlineConstants(tmp);
      return tmp;
    }
  
    /**
     * {@inheritDoc}
     *
     * Applies changes to the unique name of the given {@link SourceCodeEntity}, potentially dependent
     * on its specific {@link EntityType}.
     */
    @Override
    public String normalizeUniqueName(SourceCodeEntity _entity) {
      String toFix = _entity.getUniqueName();
      // get rid of class name for static methods
      if (_entity.getType().isStatement() && cua != null) {
        toFix = removeLeadingClassName(toFix);
      }
      // Normalizations applicable to all entities
      String tmp = this.normalizeUniqueName(toFix);
      tmp = this.removeNumberCasts(tmp);
      // Normalizations specific to entity types
  
      // Variable declaration: Remove leading "final"
  
      return tmp;
    }
  
    /**
     * <p>removeLeadingClassName.</p>
     *
     * @param _string a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String removeLeadingClassName(String _string) {
      String tmp = _string.trim();
      tmp = tmp.replaceAll(cua + "\\.", "");
      return tmp;
    }
  
    /**
     * <p>removeNumberCasts.</p>
     *
     * @param _string a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String removeNumberCasts(String _string) {
      String regex_string = "[^\"]*\"[^\"]*\"";
      Pattern pattern_string = Pattern.compile(regex_string);
      Matcher matcher_string = pattern_string.matcher(_string);
      // do not substitute when is a user defined string
      if (!matcher_string.matches()) {
        String regex = "([^0-9]*)([0-9]+)([BDFL])(.*)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(_string);
        final StringBuilder tmp = new StringBuilder();
        int i = 0;
        while (matcher.find()) {
          tmp.append(matcher.group(1)).append(matcher.group(2)).append(matcher.group(4));
          i = matcher.end();
        }
        return tmp.toString();
      } else {
        return _string;
      }
    }
  
    /**
     * <p>inlineConstants.</p>
     *
     * @param _string a {@link java.lang.String} object.
     * @return a {@link java.lang.String} object.
     */
    public String inlineConstants(String _string) {
      final StringBuilder b = new StringBuilder();
      final Matcher m = CONSTANT_PATTERN.matcher(_string);
      int idx = 0;
      String constant_name = null, constant_value = null;
      Set<Field> fields = null;
      while (m.find()) {
        constant_name = _string.substring(m.start(), m.end());
  
        // Find fields and take value
        fields = this.findConstants(m.group(1), m.group(2));
        if (fields.size() == 1) constant_value = this.getConstantValue(fields.iterator().next());
        else if (fields.size() > 1) {
          UniqueNameNormalizer.log.warn(
              fields.size() + " constants [" + constant_name + "] found, take first");
          constant_value = this.getConstantValue(fields.iterator().next());
        } else constant_value = null;
  
        b.append(_string.substring(idx, m.start()));
        if (constant_value == null) b.append(_string.substring(m.start(), m.end()));
        else b.append(constant_value);
        idx = m.end();
      }
      b.append(_string.substring(idx));
      return b.toString();
    }
  
    /**
     * Returns the value of the given {@link Field} as {@link String}.
     *
     * Todo: Until now, the method only distinguishes primitive types and
     * everything else. In both cases, the value is obtained by calling
     * toString. In the latter case, the value is additionally wrapped in quotes.
     * One must probably distinguish further cases, e.g., chars wrapper by single quotes.
     *
     * @param _field
     * @return
     */
    private String getConstantValue(Field _field) {
      final StringBuilder value = new StringBuilder();
      try {
        final Class type = _field.getType();
        _field.setAccessible(true);
        if (type.isPrimitive()) {
          value.append(_field.get(null).toString());
        } else {
          value.append("\"").append(_field.get(null).toString()).append("\"");
        }
      } catch (IllegalArgumentException e) {
        UniqueNameNormalizer.log.error(
            "Error while obtaining value of field [" + _field + "]: " + e.getMessage(), e);
      } catch (IllegalAccessException e) {
        UniqueNameNormalizer.log.error(
            "Error while accessing value of field [" + _field + "]: " + e.getMessage(), e);
      } catch (NoClassDefFoundError e2) {
      }
      return value.toString();
    }
  
    /**
     * Searches for classes or interfaces of the given name (w/o package qualifier) and which have a constant of the given name.
     *
     * Considers all classes before passed to {@link UniqueNameNormalizer#addStrings(Collection)} or the other two methods.
     * At the same time, the classes must be in the class path so that they can be loaded.
     *
     * @param _class_name
     * @param _field_name
     * @return
     */
    private Set<Field> findConstants(String _class_name, String _field_name) {
  
      // Find all classes
      final Set<Class> classes = new HashSet<Class>();
      // Replace . in class names by $
      final String class_name = _class_name.replaceAll("\\.", "\\$");
      Class cl = null;
      for (String qn : this.classNames) {
        if (qn.indexOf(class_name) != -1) {
  
          // If existing, try the member class loader (e.g., set by the Maven plugin)
          if (this.classLoader != null) {
            try {
              cl = this.classLoader.loadClass(qn);
            } catch (ClassNotFoundException e) {
            } catch (NoClassDefFoundError e2) {
            }
          }
  
          // If not existing or unsuccessful, try the system class loader
          if (cl == null) {
            try {
              cl = Class.forName(qn);
            } catch (ClassNotFoundException e) {
            } catch (NoClassDefFoundError e2) {
            }
          }
  
          // Add if found, otherwise write error message
          if (cl == null)
            UniqueNameNormalizer.log.error(
                "Error instantiating class or interface ["
                    + qn
                    + "], needed for constant ["
                    + _class_name
                    + "."
                    + _field_name
                    + "]");
          else classes.add(cl);
        }
      }
  
      // For each class, try to find a constant with the given name
      final Set<Field> fields = new HashSet<Field>();
      Field f = null;
      int mod = -1;
      for (Class c : classes) {
        try {
          f = c.getDeclaredField(_field_name);
          mod = f.getModifiers();
          if (Modifier.isStatic(mod) && Modifier.isFinal(mod)) {
            fields.add(f);
          }
        } catch (NoSuchFieldException e) {
        } catch (NoClassDefFoundError e) {
          UniqueNameNormalizer.log.error(
              "Class definition not found when searching for constant ["
                  + _field_name
                  + "] in class or interface ["
                  + c.getName()
                  + "]: "
                  + e.getMessage());
        } catch (SecurityException e) {
          UniqueNameNormalizer.log.error(
              "Security exception when searching for constant ["
                  + _field_name
                  + "] in class or interface ["
                  + c.getName()
                  + "]: "
                  + e.getMessage(),
              e);
        }
      }
      return fields;
    }
  
    /**
     * <p>setClassUnderAnalysisName.</p>
     *
     * @param _name a {@link java.lang.String} object.
     */
    public void setClassUnderAnalysisName(String _name) {
      cua = _name;
    }
  }
  