summaryrefslogtreecommitdiff
path: root/src/JSONReader.java
blob: a4af8c9a511479d296943f28e71122e40a3cbb76 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import java.util.HashMap;
import java.util.ArrayList;

import java.io.*;

/*
 * @author
 * Kian Agheli
 * 
 * References:
 * https://googlefonts.github.io/gf-guide/metadata.html
 * https://stackoverflow.com/questions/3880274/how-to-convert-the-object-to-string-in-java
 * https://stackoverflow.com/questions/8938498/get-the-index-of-a-pattern-in-a-string-using-regex
 * https://howtodoinjava.com/java/regex/start-end-of-string/
 * 
 * Date:
 * 2024-05-20
 * 
 * Purpose of class:
 * Read from and interpret JSON files.
 */

public class JSONReader extends Reader {
	/* metadata.pb is not exactly JSON, but so close to it
	that it's practically a subset of JSON with fewer quotation
	marks. If set, this is a pb file. */
	private boolean isMetadata;

	public JSONReader(File file) {
		super(file); // Call constructor.
		isMetadata = false; // By default, not metadata.
	}
	
	private String composeKey(String key) {
		/* Call static version. This has the benefit of reducing code size.
		Rather than write two separate static and instantiated versions, the
		instantiated version may call the static version. */
		return composeKey(key, this.isMetadata);
	}

	private static String composeKey(String key, boolean isMetadata) {
		/* JSON usually has quotes surrounding keys. Google's metadata doesn't. */
		if (!isMetadata) {
			key = "\"" + key + "\"";
		}
		key += ": ";
		
		return key;
	}
	
	/**
	 * Search for and return the first
	 * match.
	 */
	public String get(String key) {
		/* Call static version. */
		return JSONReader.get(this.getContents(), key, isMetadata);
	}

	public static String get(String json, String key) {
		/* No isMetadata boolean passed, presumably irrelevant. */
		return JSONReader.get(json, key, false);
	}

	/**
	 * Get a value given a key and a block of JSON to search.
	 */
	public static String get(String json, String key, boolean isMetadata) {
		key = composeKey(key, isMetadata); // Compose a key
		int start = json.indexOf(key); // Index of key
		if (start == -1) {
			/* No match. */
			return null;
		}
		
		start += key.length(); // Start of data past key
		/* This JSON is pretty-printed. */
		int nl = json.indexOf("\n", start);
		
		/* Extract substring. Remove any quotation marks and commas. */
		String substring = json.substring(start, nl);
		return substring.replaceAll("[\",]", "");
	}

	/**
	 * Search for and return all matches.
	 */
	public String[] getAll(String key) {
		key = composeKey(key); // Compose key
		
		ArrayList<String> list = new ArrayList<String>(); // ArrayList to compose output array
		int lastMatch = 0; // Previous match's index. Necessary for iteration through file contents.
		/* While matches exist, continue. */
		while ((lastMatch = this.getContents().indexOf(key, lastMatch)) != -1) {
			lastMatch += key.length(); // Data starts after key.
			int start = lastMatch;
			/* This JSON is pretty-printed. */
			int nl = this.getContents().indexOf("\n", start);
			if (nl == -1) {
				/* EOF. */
				nl = this.getContents().length();
			}
			
			/* Extract substring. Remove any quotation marks and commas. */
			String substring = this.getContents().substring(start, nl);
			list.add(substring.replaceAll("[\",]", ""));
		}
		
		/* The caller doesn't need the capabilites of an ArrayList, only
		those of an array. */
		String[] array = new String[list.size()];
		list.toArray(array);
		return array;
	}
	
	/**
	 * @return a block of JSON associated with a font family.
	 * Heavily reliant on the format of popularity.json. Not
	 * suitable for generic JSON parsing.
	 */
	public String getFamily(String family) {
		/* Taking advantage of the specific structure 
		of popularity.json. Find the index of the family
		name declaration. Then, all items until the next line
		starting with '  }' are included. */
		String contents = this.getContents();
		int start = contents.indexOf("   \"family\": \"" + family + "\",");
		if (start == -1) {
			/* No match. */
			return null;
		}

		/* Restring input starting from family name declaration. */
		contents = contents.substring(start);

		String[] lines = contents.split("\n");
		int i = 0;
		/* In this format, an end block is guaranteed.
		Iterate through lines until the desired level of end
		block is found. */
		while (!lines[i].startsWith("  }")) {
			i++;
		}

		/* For each of the lines in the block preceding the end block,
		add to output. */
		String result = "";
		for (int j = 0; j < i; j++) {
			result += lines[j] + "\n";
		}

		return result;
	}
	
	/**
	 * Specify that this is the special pb metadata format. Almost JSON.
	 * See https://googlefonts.github.io/gf-guide/metadata.html. */
	public void setMetadata() {
		this.isMetadata = true;
	}
}