JResource.java 8.8 KB
Newer Older
S
Skylot 已提交
1 2
package jadx.gui.treemodel;

S
Skylot 已提交
3
import java.util.ArrayList;
4
import java.util.Comparator;
S
Skylot 已提交
5 6 7
import java.util.List;
import java.util.Map;

8 9
import javax.swing.Icon;
import javax.swing.ImageIcon;
10

S
Skylot 已提交
11
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
12
import org.jetbrains.annotations.Nullable;
S
Skylot 已提交
13

14
import jadx.api.ICodeInfo;
S
Skylot 已提交
15
import jadx.api.ICodeWriter;
S
Skylot 已提交
16
import jadx.api.ResourceFile;
17
import jadx.api.ResourceFileContent;
S
Skylot 已提交
18
import jadx.api.ResourceType;
19
import jadx.api.ResourcesLoader;
20
import jadx.api.impl.SimpleCodeInfo;
21
import jadx.core.utils.Utils;
22
import jadx.core.xmlgen.ResContainer;
23 24
import jadx.gui.ui.TabbedPane;
import jadx.gui.ui.codearea.CodeContentPanel;
25 26
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.ui.panel.ImagePanel;
27
import jadx.gui.utils.NLS;
28
import jadx.gui.utils.UiUtils;
S
Skylot 已提交
29

30
public class JResource extends JLoadableNode {
S
Skylot 已提交
31 32
	private static final long serialVersionUID = -201018424302612434L;

33 34 35 36 37 38 39 40 41 42
	private static final ImageIcon ROOT_ICON = UiUtils.openSvgIcon("nodes/resourcesRoot");
	private static final ImageIcon FOLDER_ICON = UiUtils.openSvgIcon("nodes/folder");
	private static final ImageIcon FILE_ICON = UiUtils.openSvgIcon("nodes/file_any_type");
	private static final ImageIcon ARSC_ICON = UiUtils.openSvgIcon("nodes/resourceBundle");
	private static final ImageIcon XML_ICON = UiUtils.openSvgIcon("nodes/xml");
	private static final ImageIcon IMAGE_ICON = UiUtils.openSvgIcon("nodes/ImagesFileType");
	private static final ImageIcon SO_ICON = UiUtils.openSvgIcon("nodes/binaryFile");
	private static final ImageIcon MANIFEST_ICON = UiUtils.openSvgIcon("nodes/manifest");
	private static final ImageIcon JAVA_ICON = UiUtils.openSvgIcon("nodes/java");
	private static final ImageIcon UNKNOWN_ICON = UiUtils.openSvgIcon("nodes/unknown");
S
Skylot 已提交
43

44
	public enum JResType {
S
Skylot 已提交
45 46 47 48 49
		ROOT,
		DIR,
		FILE
	}

S
Skylot 已提交
50 51 52 53 54
	private final transient String name;
	private final transient String shortName;
	private final transient List<JResource> files = new ArrayList<>(1);
	private final transient JResType type;
	private final transient ResourceFile resFile;
S
Skylot 已提交
55

S
Skylot 已提交
56
	private transient boolean loaded;
57
	private transient ICodeInfo content;
S
Skylot 已提交
58 59

	public JResource(ResourceFile resFile, String name, JResType type) {
60 61 62 63
		this(resFile, name, name, type);
	}

	public JResource(ResourceFile resFile, String name, String shortName, JResType type) {
S
Skylot 已提交
64 65
		this.resFile = resFile;
		this.name = name;
66
		this.shortName = shortName;
S
Skylot 已提交
67
		this.type = type;
68
		this.loaded = false;
S
Skylot 已提交
69 70 71
	}

	public final void update() {
72
		if (files.isEmpty()) {
73
			if (type == JResType.DIR || type == JResType.ROOT
74
					|| resFile.getType() == ResourceType.ARSC) {
75 76
				// fake leaf to force show expand button
				// real sub nodes will load on expand in loadNode() method
77 78 79
				add(new TextNode(NLS.str("tree.loading")));
			}
		} else {
80
			removeAllChildren();
81

82
			Comparator<JResource> typeComparator = Comparator.comparingInt(r -> r.type.ordinal());
83
			Comparator<JResource> nameComparator = Comparator.comparing(JResource::getName, String.CASE_INSENSITIVE_ORDER);
84 85 86

			files.sort(typeComparator.thenComparing(nameComparator));

87 88 89 90
			for (JResource res : files) {
				res.update();
				add(res);
			}
S
Skylot 已提交
91 92 93
		}
	}

94
	@Override
95
	public synchronized void loadNode() {
96
		getCodeInfo();
97
		update();
98 99
	}

S
Skylot 已提交
100
	@Override
S
Skylot 已提交
101 102 103 104
	public String getName() {
		return name;
	}

105 106 107 108
	public JResType getType() {
		return type;
	}

S
Skylot 已提交
109 110 111 112
	public List<JResource> getFiles() {
		return files;
	}

113 114 115 116 117 118 119 120 121 122 123
	@Override
	public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) {
		if (resFile == null) {
			return null;
		}
		if (resFile.getType() == ResourceType.IMG) {
			return new ImagePanel(tabbedPane, this);
		}
		return new CodeContentPanel(tabbedPane, this);
	}

S
Skylot 已提交
124
	@Override
125
	public synchronized ICodeInfo getCodeInfo() {
126
		if (loaded) {
127
			return content;
128
		}
129 130 131 132 133 134 135
		ICodeInfo codeInfo = loadContent();
		content = codeInfo;
		loaded = true;
		return codeInfo;
	}

	private ICodeInfo loadContent() {
136
		if (resFile == null || type != JResType.FILE) {
137
			return ICodeInfo.EMPTY;
138 139
		}
		if (!isSupportedForView(resFile.getType())) {
140
			return ICodeInfo.EMPTY;
141 142 143
		}
		ResContainer rc = resFile.loadContent();
		if (rc == null) {
144
			return ICodeInfo.EMPTY;
145 146
		}
		if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
147
			ICodeInfo codeInfo = loadCurrentSingleRes(rc);
148 149
			for (ResContainer subFile : rc.getSubFiles()) {
				loadSubNodes(this, subFile, 1);
S
Skylot 已提交
150
			}
151
			return codeInfo;
S
Skylot 已提交
152
		}
153 154
		// single node
		return loadCurrentSingleRes(rc);
S
Skylot 已提交
155 156
	}

157
	private ICodeInfo loadCurrentSingleRes(ResContainer rc) {
158 159 160
		switch (rc.getDataType()) {
			case TEXT:
			case RES_TABLE:
161
				return rc.getText();
162 163 164 165 166

			case RES_LINK:
				try {
					return ResourcesLoader.decodeStream(rc.getResLink(), (size, is) -> {
						if (size > 10 * 1024 * 1024L) {
167
							return new SimpleCodeInfo("File too large for view");
168
						}
169
						return ResourcesLoader.loadToCodeWriter(is);
170 171
					});
				} catch (Exception e) {
S
Skylot 已提交
172
					return new SimpleCodeInfo("Failed to load resource file:" + ICodeWriter.NL + Utils.getStackTrace(e));
173
				}
174 175 176

			case DECODED_DATA:
			default:
177
				return new SimpleCodeInfo("Unexpected resource type: " + rc);
178
		}
179 180 181 182 183 184
	}

	private void loadSubNodes(JResource root, ResContainer rc, int depth) {
		String resName = rc.getName();
		String[] path = resName.split("/");
		String resShortName = path.length == 0 ? resName : path[path.length - 1];
185 186
		ICodeInfo code = rc.getText();
		ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, code);
187 188 189 190
		addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));

		for (ResContainer subFile : rc.getSubFiles()) {
			loadSubNodes(root, subFile, depth + 1);
191 192 193 194 195 196 197 198
		}
	}

	private static void addPath(String[] path, JResource root, JResource jResource) {
		if (path.length == 1) {
			root.getFiles().add(jResource);
			return;
		}
S
Skylot 已提交
199
		JResource currentRoot = root;
200 201 202 203
		int last = path.length - 1;
		for (int i = 0; i <= last; i++) {
			String f = path[i];
			if (i == last) {
S
Skylot 已提交
204
				currentRoot.getFiles().add(jResource);
205
			} else {
S
Skylot 已提交
206
				currentRoot = getResDir(currentRoot, f);
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
			}
		}
	}

	private static JResource getResDir(JResource root, String dirName) {
		for (JResource file : root.getFiles()) {
			if (file.getName().equals(dirName)) {
				return file;
			}
		}
		JResource resDir = new JResource(null, dirName, JResType.DIR);
		root.getFiles().add(resDir);
		return resDir;
	}

S
Skylot 已提交
222 223
	@Override
	public String getSyntaxName() {
S
Skylot 已提交
224 225 226
		if (resFile == null) {
			return null;
		}
S
Skylot 已提交
227 228 229
		switch (resFile.getType()) {
			case CODE:
				return super.getSyntaxName();
S
Skylot 已提交
230

S
Skylot 已提交
231 232
			case MANIFEST:
			case XML:
233
			case ARSC:
S
Skylot 已提交
234
				return SyntaxConstants.SYNTAX_STYLE_XML;
S
Skylot 已提交
235 236

			default:
237
				String syntax = getSyntaxByExtension(resFile.getDeobfName());
S
Skylot 已提交
238 239 240 241
				if (syntax != null) {
					return syntax;
				}
				return super.getSyntaxName();
S
Skylot 已提交
242 243 244
		}
	}

245 246 247 248 249 250 251 252 253 254 255 256
	private static final Map<String, String> EXTENSION_TO_FILE_SYNTAX = jadx.core.utils.Utils.newConstStringMap(
			"java", SyntaxConstants.SYNTAX_STYLE_JAVA,
			"js", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT,
			"ts", SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT,
			"json", SyntaxConstants.SYNTAX_STYLE_JSON,
			"css", SyntaxConstants.SYNTAX_STYLE_CSS,
			"less", SyntaxConstants.SYNTAX_STYLE_LESS,
			"html", SyntaxConstants.SYNTAX_STYLE_HTML,
			"xml", SyntaxConstants.SYNTAX_STYLE_XML,
			"yaml", SyntaxConstants.SYNTAX_STYLE_YAML,
			"properties", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE,
			"ini", SyntaxConstants.SYNTAX_STYLE_INI,
257
			"sql", SyntaxConstants.SYNTAX_STYLE_SQL);
258

S
Skylot 已提交
259 260 261 262 263 264
	private String getSyntaxByExtension(String name) {
		int dot = name.lastIndexOf('.');
		if (dot == -1) {
			return null;
		}
		String ext = name.substring(dot + 1);
265
		return EXTENSION_TO_FILE_SYNTAX.get(ext);
S
Skylot 已提交
266 267 268 269 270 271 272 273 274 275 276 277
	}

	@Override
	public Icon getIcon() {
		switch (type) {
			case ROOT:
				return ROOT_ICON;
			case DIR:
				return FOLDER_ICON;

			case FILE:
				ResourceType resType = resFile.getType();
278
				switch (resType) {
279 280 281 282 283 284 285 286 287 288 289 290 291 292
					case MANIFEST:
						return MANIFEST_ICON;
					case ARSC:
						return ARSC_ICON;
					case XML:
						return XML_ICON;
					case IMG:
						return IMAGE_ICON;
					case LIB:
						return SO_ICON;
					case CODE:
						return JAVA_ICON;
					case UNKNOWN:
						return UNKNOWN_ICON;
S
Skylot 已提交
293
				}
294
				return UNKNOWN_ICON;
S
Skylot 已提交
295 296 297 298
		}
		return FILE_ICON;
	}

S
Skylot 已提交
299
	public static boolean isSupportedForView(ResourceType type) {
S
Skylot 已提交
300 301 302 303
		switch (type) {
			case CODE:
			case FONT:
			case LIB:
304
			case MEDIA:
S
Skylot 已提交
305 306 307
				return false;

			case MANIFEST:
308
			case XML:
309
			case ARSC:
S
Skylot 已提交
310
			case IMG:
S
Skylot 已提交
311 312 313 314 315 316
			case UNKNOWN:
				return true;
		}
		return true;
	}

S
Skylot 已提交
317 318 319 320
	public ResourceFile getResFile() {
		return resFile;
	}

S
Skylot 已提交
321 322 323 324 325 326
	@Override
	public JClass getJParent() {
		return null;
	}

	@Override
327 328
	public String makeString() {
		return shortName;
S
Skylot 已提交
329 330 331
	}

	@Override
332 333
	public String makeLongString() {
		return name;
S
Skylot 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}
		return name.equals(((JResource) o).name);
	}

	@Override
	public int hashCode() {
		return name.hashCode();
	}
}