mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-23 08:35:34 +00:00
635 lines
17 KiB
Java
635 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2010-2024 JPEXS, All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 3.0 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library.
|
|
*/
|
|
package com.jpexs.decompiler.graph;
|
|
|
|
import com.jpexs.decompiler.flash.IdentifiersDeobfuscation;
|
|
import com.jpexs.helpers.Helper;
|
|
import java.io.File;
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* Dotted chain class. Represents a chain of names separated by dots.
|
|
*
|
|
* @author JPEXS
|
|
*/
|
|
public class DottedChain implements Serializable, Comparable<DottedChain> {
|
|
|
|
//Basic dotted chains
|
|
public static final DottedChain EMPTY = new DottedChain(true);
|
|
|
|
public static final DottedChain UNBOUNDED = new DottedChain(new String[]{"*"});
|
|
|
|
public static final DottedChain TOPLEVEL = new DottedChain(new String[]{});
|
|
|
|
public static final DottedChain BOOLEAN = new DottedChain(new String[]{"Boolean"});
|
|
|
|
public static final DottedChain STRING = new DottedChain(new String[]{"String"});
|
|
|
|
public static final DottedChain ARRAY = new DottedChain(new String[]{"Array"});
|
|
|
|
public static final DottedChain NUMBER = new DottedChain(new String[]{"Number"});
|
|
|
|
public static final DottedChain DECIMAL = new DottedChain(new String[]{"decimal"});
|
|
|
|
public static final DottedChain OBJECT = new DottedChain(new String[]{"Object"});
|
|
|
|
public static final DottedChain INT = new DottedChain(new String[]{"int"});
|
|
|
|
public static final DottedChain UINT = new DottedChain(new String[]{"uint"});
|
|
|
|
public static final DottedChain UNDEFINED = new DottedChain(new String[]{"Undefined"});
|
|
|
|
public static final DottedChain XML = new DottedChain(new String[]{"XML"});
|
|
|
|
public static final DottedChain NULL = new DottedChain(new String[]{"null"});
|
|
|
|
public static final DottedChain FUNCTION = new DottedChain(new String[]{"Function"});
|
|
|
|
public static final DottedChain VOID = new DottedChain(new String[]{"void"});
|
|
|
|
public static final DottedChain NAMESPACE = new DottedChain(new String[]{"Namespace"});
|
|
|
|
public static final DottedChain ALL = new DottedChain(new String[]{"*"});
|
|
|
|
/**
|
|
* Parts of the chain.
|
|
*/
|
|
private List<PathPart> parts;
|
|
|
|
/**
|
|
* Is this chain null?
|
|
*/
|
|
private boolean isNull = false;
|
|
|
|
/**
|
|
* Get the namespace suffix of the part at the given index.
|
|
*
|
|
* @param index Index
|
|
* @return Namespace suffix
|
|
*/
|
|
public String getNamespaceSuffix(int index) {
|
|
return parts.get(index).namespaceSuffix;
|
|
}
|
|
|
|
/**
|
|
* Gets the last namespace suffix.
|
|
*
|
|
* @return Last namespace suffix
|
|
*/
|
|
public String getLastNamespaceSuffix() {
|
|
if (parts.isEmpty()) {
|
|
return "";
|
|
}
|
|
return parts.get(parts.size() - 1).namespaceSuffix;
|
|
}
|
|
|
|
/**
|
|
* Parses a dotted chain from a string without suffix.
|
|
*
|
|
* @param name Name
|
|
* @return Dotted chain
|
|
*/
|
|
public static final DottedChain parseNoSuffix(String name) {
|
|
if (name == null) {
|
|
return DottedChain.EMPTY;
|
|
} else if (name.isEmpty()) {
|
|
return DottedChain.TOPLEVEL;
|
|
} else {
|
|
String[] parts = name.split("\\.");
|
|
List<PathPart> newParts = new ArrayList<>();
|
|
for (String part : parts) {
|
|
newParts.add(new PathPart(part, false, ""));
|
|
}
|
|
|
|
return new DottedChain(newParts, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses a dotted chain from a string with suffix.
|
|
*
|
|
* @param name Name
|
|
* @return Dotted chain
|
|
*/
|
|
public static final DottedChain parseWithSuffix(String name) {
|
|
if (name == null) {
|
|
return DottedChain.EMPTY;
|
|
} else if (name.isEmpty()) {
|
|
return DottedChain.TOPLEVEL;
|
|
} else {
|
|
String[] parts = name.split("\\.");
|
|
List<PathPart> newParts = new ArrayList<>();
|
|
for (String part : parts) {
|
|
String nameNoSuffix = part;
|
|
String namespaceSuffix = "";
|
|
if (part.matches(".*#[0-9]+$")) {
|
|
nameNoSuffix = part.substring(0, part.lastIndexOf("#"));
|
|
namespaceSuffix = part.substring(part.lastIndexOf("#"));
|
|
}
|
|
newParts.add(new PathPart(nameNoSuffix, false, namespaceSuffix));
|
|
}
|
|
|
|
return new DottedChain(newParts, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a new dotted chain.
|
|
*
|
|
* @param isNull Whether the chain is null
|
|
*/
|
|
private DottedChain(boolean isNull) {
|
|
this.isNull = isNull;
|
|
this.parts = new ArrayList<>();
|
|
}
|
|
|
|
/**
|
|
* Constructs a new dotted chain.
|
|
*
|
|
* @param src Source chain
|
|
*/
|
|
public DottedChain(DottedChain src) {
|
|
this.parts = new ArrayList<>(src.parts);
|
|
this.isNull = src.isNull;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new dotted chain.
|
|
*
|
|
* @param parts Parts
|
|
*/
|
|
public DottedChain(String[] parts) {
|
|
this(Arrays.asList(parts));
|
|
}
|
|
|
|
/**
|
|
* Constructs a new dotted chain.
|
|
*
|
|
* @param parts Parts
|
|
* @param isNull Whether the chain is null
|
|
*/
|
|
private DottedChain(List<PathPart> parts, boolean isNull) {
|
|
this.parts = parts;
|
|
this.isNull = isNull;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new dotted chain.
|
|
*
|
|
* @param parts Parts
|
|
*/
|
|
public DottedChain(List<String> parts) {
|
|
List<PathPart> newParts = new ArrayList<>();
|
|
for (String part : parts) {
|
|
newParts.add(new PathPart(part, false, ""));
|
|
}
|
|
this.parts = newParts;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new dotted chain.
|
|
*
|
|
* @param parts Parts
|
|
* @param namespaceSuffixes Namespace suffixes
|
|
*/
|
|
public DottedChain(String[] parts, String[] namespaceSuffixes) {
|
|
this(new boolean[parts.length], parts, namespaceSuffixes);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new dotted chain.
|
|
*
|
|
* @param attributes Attributes
|
|
* @param parts Parts
|
|
* @param namespaceSuffixes Namespace suffixes
|
|
*/
|
|
public DottedChain(boolean[] attributes, String[] parts, String[] namespaceSuffixes) {
|
|
List<PathPart> newParts = new ArrayList<>();
|
|
for (int i = 0; i < attributes.length; i++) {
|
|
newParts.add(new PathPart(parts[i], attributes[i], namespaceSuffixes[i]));
|
|
}
|
|
this.parts = newParts;
|
|
|
|
}
|
|
|
|
/**
|
|
* Checks whether this chain is top-level.
|
|
*
|
|
* @return Whether this chain is top-level
|
|
*/
|
|
public boolean isTopLevel() {
|
|
return !isNull && parts.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Checks whether this chain is empty.
|
|
*
|
|
* @return Whether this chain is empty
|
|
*/
|
|
public boolean isEmpty() {
|
|
return isNull;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of parts in this chain.
|
|
*
|
|
* @return Number of parts
|
|
*/
|
|
public int size() {
|
|
return parts.size();
|
|
}
|
|
|
|
/**
|
|
* Gets the part at the given index.
|
|
*
|
|
* @param index Index
|
|
* @return Part
|
|
*/
|
|
public String get(int index) {
|
|
if (index >= parts.size()) {
|
|
throw new ArrayIndexOutOfBoundsException();
|
|
}
|
|
|
|
return parts.get(index).name;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the part at the given index is an attribute.
|
|
*
|
|
* @param index Index
|
|
* @return Whether the part at the given index is an attribute
|
|
*/
|
|
public boolean isAttribute(int index) {
|
|
if (index >= parts.size()) {
|
|
throw new ArrayIndexOutOfBoundsException();
|
|
}
|
|
return parts.get(index).attribute;
|
|
}
|
|
|
|
/**
|
|
* Gets the sub-chain of specific length.
|
|
*
|
|
* @param count Length
|
|
* @return Sub-chain
|
|
*/
|
|
public DottedChain subChain(int count) {
|
|
if (count > parts.size()) {
|
|
throw new ArrayIndexOutOfBoundsException();
|
|
}
|
|
|
|
return new DottedChain(new ArrayList<>(parts.subList(0, count)), isNull);
|
|
}
|
|
|
|
/**
|
|
* Gets last part.
|
|
*
|
|
* @return Last part
|
|
*/
|
|
public String getLast() {
|
|
if (isNull) {
|
|
return null;
|
|
}
|
|
if (parts.isEmpty()) {
|
|
return "";
|
|
} else {
|
|
return parts.get(parts.size() - 1).name;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether the last part is an attribute.
|
|
*
|
|
* @return Whether the last part is an attribute
|
|
*/
|
|
public boolean isLastAttribute() {
|
|
if (isNull) {
|
|
return false;
|
|
}
|
|
if (parts.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
return parts.get(parts.size() - 1).attribute;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the chain without the last part.
|
|
*
|
|
* @return Chain without the last part
|
|
*/
|
|
public DottedChain getWithoutLast() {
|
|
if (isNull) {
|
|
return null;
|
|
}
|
|
if (parts.size() < 2) {
|
|
return EMPTY;
|
|
}
|
|
|
|
return subChain(parts.size() - 1);
|
|
}
|
|
|
|
/**
|
|
* Adds a part to the chain with a suffix.
|
|
*
|
|
* @param name Name
|
|
* @return New chain
|
|
*/
|
|
public DottedChain addWithSuffix(String name) {
|
|
String addedNameNoSuffix = name;
|
|
String addedNamespaceSuffix = "";
|
|
if (name != null && name.matches(".*#[0-9]+$")) {
|
|
addedNameNoSuffix = name.substring(0, name.lastIndexOf("#"));
|
|
addedNamespaceSuffix = name.substring(name.lastIndexOf("#"));
|
|
}
|
|
return add(addedNameNoSuffix, addedNamespaceSuffix);
|
|
}
|
|
|
|
/**
|
|
* Adds a part to the chain.
|
|
*
|
|
* @param name Name
|
|
* @param namespaceSuffix Namespace suffix
|
|
* @return New chain
|
|
*/
|
|
public DottedChain add(String name, String namespaceSuffix) {
|
|
return add(false, name, namespaceSuffix);
|
|
}
|
|
|
|
/**
|
|
* Adds a part to the chain.
|
|
*
|
|
* @param attribute Whether the part is an attribute
|
|
* @param name Name
|
|
* @param namespaceSuffix Namespace suffix
|
|
* @return New chain
|
|
*/
|
|
public DottedChain add(boolean attribute, String name, String namespaceSuffix) {
|
|
if (name == null) {
|
|
return new DottedChain(this);
|
|
}
|
|
List<PathPart> newParts = new ArrayList<>(parts);
|
|
newParts.add(new PathPart(name, attribute, namespaceSuffix));
|
|
return new DottedChain(newParts, false);
|
|
}
|
|
|
|
/**
|
|
* Adds prefix to the chain.
|
|
*
|
|
* @param name Name
|
|
* @param namespaceSuffix Namespace suffix
|
|
* @return New chain
|
|
*/
|
|
public DottedChain preAdd(String name, String namespaceSuffix) {
|
|
return preAdd(false, name, namespaceSuffix);
|
|
}
|
|
|
|
/**
|
|
* Adds prefix to the chain.
|
|
*
|
|
* @param attribute Whether the part is an attribute
|
|
* @param name Name
|
|
* @param namespaceSuffix Namespace suffix
|
|
* @return New chain
|
|
*/
|
|
public DottedChain preAdd(boolean attribute, String name, String namespaceSuffix) {
|
|
if (name == null) {
|
|
return new DottedChain(this);
|
|
}
|
|
List<PathPart> newParts = new ArrayList<>(parts);
|
|
newParts.add(0, new PathPart(name, attribute, namespaceSuffix));
|
|
return new DottedChain(newParts, false);
|
|
}
|
|
|
|
/**
|
|
* To string.
|
|
*
|
|
* @return String
|
|
*/
|
|
@Override
|
|
public String toString() {
|
|
return toRawString();
|
|
}
|
|
|
|
/**
|
|
* To string.
|
|
*
|
|
* @param as3 Whether to print as AS3
|
|
* @param raw Whether to print raw (without deobfuscation)
|
|
* @param withSuffix Whether to print with suffix
|
|
* @return String
|
|
*/
|
|
protected String toString(boolean as3, boolean raw, boolean withSuffix) {
|
|
if (isNull) {
|
|
return "";
|
|
}
|
|
if (parts.isEmpty()) {
|
|
return "";
|
|
}
|
|
|
|
StringBuilder ret = new StringBuilder();
|
|
for (int i = 0; i < parts.size(); i++) {
|
|
if (i > 0) {
|
|
ret.append(".");
|
|
}
|
|
if (parts.get(i).attribute) {
|
|
ret.append("@");
|
|
}
|
|
String part = parts.get(i).name;
|
|
boolean lastStar = i == parts.size() - 1 && "*".equals(part);
|
|
ret.append((raw || lastStar) ? part : IdentifiersDeobfuscation.printIdentifier(as3, part));
|
|
if (withSuffix) {
|
|
ret.append(parts.get(i).namespaceSuffix);
|
|
}
|
|
}
|
|
|
|
return ret.toString();
|
|
}
|
|
|
|
/**
|
|
* To file path.
|
|
*
|
|
* @return File path
|
|
*/
|
|
public String toFilePath() {
|
|
if (isNull) {
|
|
return "";
|
|
}
|
|
if (parts.isEmpty()) {
|
|
return "";
|
|
}
|
|
|
|
StringBuilder ret = new StringBuilder();
|
|
for (int i = 0; i < parts.size(); i++) {
|
|
if (i > 0) {
|
|
ret.append(File.separator);
|
|
}
|
|
|
|
ret.append(Helper.makeFileName(IdentifiersDeobfuscation.printIdentifier(true, parts.get(i).name)));
|
|
}
|
|
return ret.toString();
|
|
}
|
|
|
|
/**
|
|
* To list.
|
|
*
|
|
* @return List
|
|
*/
|
|
public List<String> toList() {
|
|
List<String> ret = new ArrayList<>();
|
|
for (PathPart p : parts) {
|
|
ret.add(p.name);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* To printable string.
|
|
*
|
|
* @param as3 Whether to print as AS3
|
|
* @return Printable string
|
|
*/
|
|
public String toPrintableString(boolean as3) {
|
|
return toString(as3, false, true);
|
|
}
|
|
|
|
/**
|
|
* To raw string. (without deobfuscation)
|
|
*
|
|
* @return Raw string
|
|
*/
|
|
public String toRawString() { //Is SUFFIX correctly handled?
|
|
return toString(false/*ignored*/, true, true);
|
|
}
|
|
|
|
/**
|
|
* Hash code.
|
|
*
|
|
* @return Hash code
|
|
*/
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = 7;
|
|
hash = 41 * hash + Objects.hashCode(this.parts);
|
|
hash = 41 * hash + (this.isNull ? 1 : 0);
|
|
return hash;
|
|
}
|
|
|
|
/**
|
|
* Equals.
|
|
*
|
|
* @param obj Object
|
|
* @return Whether this object is equal to the given object
|
|
*/
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj == null) {
|
|
return false;
|
|
}
|
|
if (getClass() != obj.getClass()) {
|
|
return false;
|
|
}
|
|
final DottedChain other = (DottedChain) obj;
|
|
if (this.isNull != other.isNull) {
|
|
return false;
|
|
}
|
|
return Objects.equals(this.parts, other.parts);
|
|
}
|
|
|
|
/**
|
|
* Compare to.
|
|
*
|
|
* @param o the object to be compared.
|
|
* @return a negative integer, zero, or a positive integer as this object is
|
|
* less than, equal to, or greater than the specified object.
|
|
*/
|
|
@Override
|
|
public int compareTo(DottedChain o) {
|
|
return toRawString().compareTo(o.toRawString());
|
|
}
|
|
|
|
/**
|
|
* Path part class.
|
|
*/
|
|
private static class PathPart implements Serializable {
|
|
|
|
/**
|
|
* Name.
|
|
*/
|
|
public String name;
|
|
|
|
/**
|
|
* Is this part an attribute?
|
|
*/
|
|
public boolean attribute;
|
|
|
|
/**
|
|
* Namespace suffix.
|
|
*/
|
|
public String namespaceSuffix;
|
|
|
|
/**
|
|
* Constructs a new path part.
|
|
*
|
|
* @param name Name
|
|
* @param attribute Whether this part is an attribute
|
|
* @param namespaceSuffix Namespace suffix
|
|
*/
|
|
public PathPart(String name, boolean attribute, String namespaceSuffix) {
|
|
this.name = name;
|
|
this.attribute = attribute;
|
|
this.namespaceSuffix = namespaceSuffix;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = 5;
|
|
hash = 79 * hash + Objects.hashCode(this.name);
|
|
hash = 79 * hash + (this.attribute ? 1 : 0);
|
|
hash = 79 * hash + Objects.hashCode(this.namespaceSuffix);
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj == null) {
|
|
return false;
|
|
}
|
|
if (getClass() != obj.getClass()) {
|
|
return false;
|
|
}
|
|
final PathPart other = (PathPart) obj;
|
|
if (this.attribute != other.attribute) {
|
|
return false;
|
|
}
|
|
if (!Objects.equals(this.name, other.name)) {
|
|
return false;
|
|
}
|
|
return Objects.equals(this.namespaceSuffix, other.namespaceSuffix);
|
|
}
|
|
}
|
|
}
|