public static Vocabulary fromTokenNames(String[] tokenNames) {
		if (tokenNames == null || tokenNames.length == 0) {
			return EMPTY_VOCABULARY;
		}

		String[] literalNames = Arrays.copyOf(tokenNames, tokenNames.length);
		String[] symbolicNames = Arrays.copyOf(tokenNames, tokenNames.length);
		for (int i = 0; i < tokenNames.length; i++) {
			String tokenName = tokenNames[i];
			if (tokenName == null) {
				continue;
			}

			if (!tokenName.isEmpty()) {
				char firstChar = tokenName.charAt(0);
				if (firstChar == '\'') {
					symbolicNames[i] = null;
					continue;
				}
				else if (Character.isUpperCase(firstChar)) {
					literalNames[i] = null;
					continue;
				}
			}

			// wasn't a literal or symbolic name
			literalNames[i] = null;
			symbolicNames[i] = null;
		}

		return new VocabularyImpl(literalNames, symbolicNames, tokenNames);
	}
Returns a {@link VocabularyImpl} instance from the specified set of token
names. This method acts as a compatibility layer for the single
{@code tokenNames} array generated by previous releases of ANTLR.

<p>The resulting vocabulary instance returns {@code null} for
{@link #getLiteralName(int)} and {@link #getSymbolicName(int)}, and the
value from {@code tokenNames} for the display names.</p>

@param tokenNames The token names, or {@code null} if no token names are
available.
@return A {@link Vocabulary} instance which uses {@code tokenNames} for
the display names of tokens.
runtime/Java/src/org/antlr/v4/runtime/VocabularyImpl.java