Created
October 26, 2023 05:14
-
-
Save MasterTuto/b0ad969af27748c52e469dca43cbd413 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main; | |
import java.util.ArrayList; | |
import java.util.Deque; | |
import java.util.HashMap; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Optional; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
import javax.print.DocFlavor.STRING; | |
class Teste { | |
Map<String, String> SENIOR_ENTITY = Map.of( | |
"<selfid>", "id" | |
); | |
Map<String, String> MID_ENTITY = Map.of( | |
"<selfid>", "id", | |
"senior", "seniorid" | |
); | |
Map<String, String> JUNIOR_ENTITY = Map.of( | |
"<selfid>", "id", | |
"midlevel", "midlevelid" | |
); | |
Map<String, Map<String, String>> ENTITIES = Map.of( | |
"senior", SENIOR_ENTITY, | |
"midlevel", MID_ENTITY, | |
"junior", JUNIOR_ENTITY | |
); | |
List<String> hiearchy = List.of("senior", "midlevel", "junior"); | |
void test() { | |
// tests of single entities | |
testSpecificPath(); | |
testTreeOfSingleEntity(); | |
testForestOfSingleEntity(); | |
testTreeOfTwoEntities(); | |
testForestOfTwoEntities(); | |
} | |
void testSpecificPath() { | |
String selector = ".senior#7 .midlevel + .midlevel#2[name='John'] "; | |
String desiredOutput = | |
"select midlevel2 from midlevel2" + | |
"left outer join"; | |
// assert toSql(selector, ENTITIES) == desiredOutput; | |
} | |
void testTreeOfSingleEntity() { | |
} | |
void testForestOfSingleEntity() { | |
String selector = ".senior#123[a=2];"; | |
String desiredOutput = | |
"select * from senior \n" + | |
" left outer join midlevel on midlevel.seniorid = senior.id \n" + | |
" left outer join junior on junior.midlevelid = midlevel.id;\n"; | |
test(selector, desiredOutput); | |
} | |
void testTreeOfTwoEntities() { | |
} | |
void testForestOfTwoEntities() { | |
} | |
void test(String cssSelector, String desiredOutput) { | |
System.out.println("\nINPUT:\n"); | |
System.out.println(cssSelector); | |
System.out.println("========================================"); | |
System.out.println("\nEXPECTED OUTPUT:\n"); | |
System.out.println(desiredOutput); | |
System.out.println("========================================"); | |
System.out.println("\nACTUAL OUTPUT:\n"); | |
System.out.println(toSql(cssSelector, ENTITIES, hiearchy)); | |
System.out.println(); | |
} | |
String toSql(String cssSelector, Map<String, Map<String, String>> parenting, List<String> hierarchy) { | |
var parsedSelector = new CSSSelector(cssSelector); | |
return toSql(parsedSelector, parenting, hierarchy); | |
} | |
String toSql(CSSSelector parsedSelector, Map<String, Map<String, String>> parenting, List<String> hierarchy) { | |
StringBuilder outputQuery = new StringBuilder(); | |
var selectPart = "select * from %s\n"; | |
var leftOuterPart = "\tleft outer join %s on %s.%s = %s.%s\n"; | |
var wherePart = "\twhere 1=1\n"; | |
var conditionPart = "\tand %s.%s = '%s'\n"; | |
for (var rule: parsedSelector.getRules()) { | |
var members = rule.getMembers(); | |
var firstMember = members.peek(); | |
var newMembers = hierarchy.stream() | |
.dropWhile(e -> !e.equals(firstMember.getName())) | |
.map((s) -> { | |
var members2 = new CSSDOMMember(s); | |
return members2; | |
}); | |
var listedNewMembers = Stream.concat(Stream.of(firstMember), newMembers).collect(Collectors.toList()); | |
boolean isFirstMember = true; | |
String previousName = null; | |
StringBuilder conditionsBuilder = new StringBuilder(); | |
for (var member: listedNewMembers) { | |
var name = member.getName(); | |
if (isFirstMember) | |
outputQuery.append(String.format(selectPart, name)); | |
else { | |
var mappig = parenting.get(name); | |
var parent = mappig.get(previousName); | |
if (parent != null) | |
outputQuery.append(String.format(leftOuterPart, name, name, parent, previousName, parenting.get(previousName).get("<selfid>"))); | |
} | |
var idColumn = parenting.get(name).get("<selfid>"); | |
var idValue = member.getIdName(); | |
if (idValue != null) { | |
conditionsBuilder.append(String.format(conditionPart, name, idColumn, idValue)); | |
} | |
var conditions = member.getConditions(); | |
for (var condition: conditions) { | |
conditionsBuilder.append(String.format(conditionPart, name, condition.getName(), condition.getValue())); | |
} | |
isFirstMember = false; | |
previousName = name; | |
} | |
outputQuery.append(wherePart); | |
if (conditionsBuilder.length() > 0) { | |
outputQuery.append(conditionsBuilder); | |
} | |
outputQuery.append(";\n"); | |
} | |
return outputQuery.toString(); | |
} | |
Optional<String> getNameFromStream(Stream<String> stream, String nameToFind) { | |
return stream.dropWhile(name -> !name.equalsIgnoreCase(nameToFind)).findFirst(); | |
} | |
enum CSSState { | |
CLASS, | |
CLASSNAME, | |
ID, | |
IDNAME, | |
ATTRIBUTEOPEN, | |
ATTRIBUTENAME, | |
ATTRIBUTEEQUALS, | |
ATTRIBUTEVALUE, | |
ATTRIBUTECLOSE, | |
ENDSELECTOR, | |
ENDRULE, | |
WHITESPACE | |
} | |
class Condition { | |
private String name; | |
private String value; | |
Condition(String name) { | |
this.name = name; | |
} | |
public String getName() { | |
return name; | |
} | |
public String getValue() { | |
return value; | |
} | |
public void setValue(String value) { | |
this.value = value; | |
} | |
} | |
class CSSDOMMember { | |
private String name; | |
private Deque<Condition> conditions = new LinkedList<>(); | |
private String idName; | |
CSSDOMMember(String name) { | |
this.name = name; | |
} | |
public String getName() { | |
return name; | |
} | |
public void addCondition(String attributeName) { | |
var newCondition = new Condition(attributeName); | |
conditions.addLast(newCondition); | |
} | |
public Condition getNewestCondition() { | |
return conditions.getLast(); | |
} | |
public String getIdName() { | |
return idName; | |
} | |
public void setIdName(String idName) { | |
this.idName = idName; | |
} | |
public Deque<Condition> getConditions() { | |
return conditions; | |
} | |
} | |
class CSSRule { | |
private Deque<CSSDOMMember> members = new LinkedList<>(); | |
private void addMember(String name) { | |
var member = new CSSDOMMember(name); | |
members.addLast(member); | |
} | |
private CSSDOMMember getNewestMember() { | |
return members.getLast(); | |
} | |
public Deque<CSSDOMMember> getMembers() { | |
return members; | |
} | |
} | |
class CSSSelector { | |
private Deque<CSSRule> rules = new LinkedList<>(); | |
private String selector; | |
CSSSelector(String selector) { | |
this.selector = selector; | |
parse(); | |
} | |
private void parse() { | |
var state = CSSState.WHITESPACE; | |
CSSState previousState = null; | |
String currentValue = ""; | |
rules.addLast(new CSSRule()); | |
for (int tokenIndex = 0; tokenIndex < this.selector.length(); tokenIndex++) { | |
var token = this.selector.charAt(tokenIndex); | |
if (!state.equals(CSSState.WHITESPACE)) | |
previousState = state; | |
switch (state) { | |
case CLASS: | |
assertToken(token, ".", "Invalid class token '.' ", tokenIndex); | |
break; | |
case CLASSNAME: | |
assertPrintable(token); | |
currentValue += token; | |
break; | |
case ATTRIBUTENAME: | |
assertPrintable(token); | |
currentValue += token; | |
break; | |
case IDNAME: | |
assertPrintable(token); | |
currentValue += token; | |
break; | |
case ATTRIBUTEVALUE: | |
currentValue += token; | |
break; | |
case WHITESPACE: | |
if (!Character.isWhitespace(tokenIndex) && previousState != null) { | |
quit("Expected a whitespace!!"); | |
} else if (!Character.isWhitespace(tokenIndex) && previousState == null) { | |
tokenIndex--; | |
} | |
break; | |
case ATTRIBUTECLOSE: | |
assertToken(token, "]", "Expected attribute close", tokenIndex); | |
break; | |
case ATTRIBUTEEQUALS: | |
assertToken(token, "=", "Expected attribute equals", tokenIndex); | |
break; | |
case ATTRIBUTEOPEN: | |
assertToken(token, "[", "Expected attribute open '['", tokenIndex); | |
break; | |
case ENDRULE: | |
assertToken(token, ",", "Expected rule end ','", tokenIndex); | |
break; | |
case ENDSELECTOR: | |
assertToken(token, ";", "Expected selector end ';'", tokenIndex); | |
tokenIndex = this.selector.length(); // end the loop | |
break; | |
case ID: | |
assertToken(token, "#", "Expected id selector '#'", tokenIndex); | |
break; | |
} | |
var newState = transition(state, previousState, tokenIndex); | |
if (state != newState && (previousState == CSSState.ATTRIBUTENAME || previousState == CSSState.CLASSNAME || previousState == CSSState.IDNAME || previousState == CSSState.ATTRIBUTEVALUE)) { // mudou de State | |
var verifyEmpty = true; | |
if (state.equals(CSSState.ATTRIBUTEVALUE)) | |
verifyEmpty = false; | |
addToRule(state, currentValue, verifyEmpty); | |
currentValue = ""; | |
} | |
state = newState; | |
} | |
} | |
private CSSState transition(CSSState state, CSSState previouState, int tokenIndex) { | |
if (state == CSSState.ENDSELECTOR) { // if is finalizing | |
return null; // doesn't matter, i will end the loop | |
} | |
if (tokenIndex >= this.selector.length() - 1) { | |
quit("Syntax erro, EOF unexpected"); | |
} | |
if (state == CSSState.CLASS) { | |
return CSSState.CLASSNAME; | |
} | |
if (state == CSSState.CLASSNAME) { | |
if (Character.isWhitespace(nextChar(tokenIndex))) { | |
return CSSState.WHITESPACE; | |
} | |
if (nextChar(tokenIndex) == '[') { | |
return CSSState.ATTRIBUTEOPEN; | |
} | |
if (nextChar(tokenIndex) == '#') { | |
return CSSState.ID; | |
} | |
if (nextChar(tokenIndex) == ',') { | |
return CSSState.ENDRULE; | |
} | |
if (nextChar(tokenIndex) == ';') { | |
return CSSState.ENDSELECTOR; | |
} | |
return CSSState.CLASSNAME; | |
} | |
if (state == CSSState.ATTRIBUTEOPEN) { | |
if (Character.isWhitespace(nextChar(tokenIndex))) { | |
return CSSState.WHITESPACE; | |
} | |
return CSSState.ATTRIBUTENAME; | |
} | |
if (state == CSSState.ID) { | |
return CSSState.IDNAME; | |
} | |
if (state == CSSState.IDNAME) { | |
if (Character.isWhitespace(nextChar(tokenIndex))) { | |
return CSSState.WHITESPACE; | |
} | |
if (nextChar(tokenIndex) == '[') { | |
return CSSState.ATTRIBUTEOPEN; | |
} | |
if (nextChar(tokenIndex) == ',') { | |
return CSSState.ENDRULE; | |
} | |
if (nextChar(tokenIndex) == ';') { | |
return CSSState.ENDSELECTOR; | |
} | |
return CSSState.IDNAME; | |
} | |
if (state == CSSState.ATTRIBUTEEQUALS) { | |
if (nextChar(tokenIndex) == ']') { | |
return CSSState.ATTRIBUTECLOSE; | |
} | |
return CSSState.ATTRIBUTEVALUE; | |
} | |
if (state == CSSState.ATTRIBUTEVALUE) { | |
if (nextChar(tokenIndex) == ']') { | |
return CSSState.ATTRIBUTECLOSE; | |
} | |
return CSSState.ATTRIBUTEVALUE; | |
} | |
if (state == CSSState.ATTRIBUTECLOSE) { | |
if (Character.isWhitespace(nextChar(tokenIndex))) { | |
return CSSState.WHITESPACE; | |
} | |
if (nextChar(tokenIndex) == ',') { | |
return CSSState.ENDRULE; | |
} | |
if (nextChar(tokenIndex) == ';') { | |
return CSSState.ENDSELECTOR; | |
} | |
return CSSState.ATTRIBUTEOPEN; | |
} | |
if (state == CSSState.WHITESPACE) { | |
if (Character.isWhitespace(nextChar(tokenIndex))) { | |
return CSSState.WHITESPACE; | |
} | |
if ((previouState == null || previouState == CSSState.ENDRULE) && nextChar(tokenIndex) != '.') { | |
quit("SyntaxError, expected begin of Rule."); | |
} | |
if ((previouState == null || previouState == CSSState.ENDRULE) && nextChar(tokenIndex) == '.') { | |
return CSSState.CLASS; | |
} | |
if (previouState == null && !Character.isWhitespace(this.selector.charAt(tokenIndex))) { | |
return CSSState.CLASS; | |
} | |
if (previouState == CSSState.IDNAME || previouState == CSSState.CLASSNAME || previouState == CSSState.ATTRIBUTECLOSE || previouState == CSSState.ENDRULE) { | |
return CSSState.CLASS; | |
} | |
if (previouState == CSSState.ATTRIBUTENAME) { | |
return CSSState.ATTRIBUTEEQUALS; | |
} | |
if (previouState == CSSState.ATTRIBUTEOPEN) { | |
return CSSState.ATTRIBUTENAME; | |
} | |
if (nextChar(tokenIndex) == ',') { | |
return CSSState.ENDRULE; | |
} | |
if (nextChar(tokenIndex) == ';') { | |
return CSSState.ENDSELECTOR; | |
} | |
} | |
if (state == CSSState.ATTRIBUTENAME) { | |
if (Character.isWhitespace(nextChar(tokenIndex))) { | |
return CSSState.WHITESPACE; | |
} | |
if (nextChar(tokenIndex) == '=') { | |
return CSSState.ATTRIBUTEEQUALS; | |
} | |
return CSSState.ATTRIBUTENAME; | |
} | |
if (state == CSSState.ENDRULE) { | |
if (Character.isWhitespace(nextChar(tokenIndex))) { | |
return CSSState.WHITESPACE; | |
} | |
return CSSState.ID; | |
} | |
quit("Unexpected behavior"); | |
return null; | |
} | |
private char nextChar(int index) { | |
try { | |
return this.selector.charAt(index + 1); | |
} catch (IndexOutOfBoundsException indexOutOfBoundsException) { | |
quit("Unexpected EOF"); | |
throw new IndexOutOfBoundsException(index); | |
} | |
} | |
private void assertPrintable(char token) { | |
if (!Character.isAlphabetic(token) && !Character.isDigit(token)) { | |
quit("Blank class, attribute or id names"); | |
} | |
} | |
private void addToRule(CSSState state, String currentValue, boolean verifyEmpty) { | |
if (currentValue.isEmpty() && verifyEmpty) { | |
quit("Syntax error!!!"); | |
} | |
var newestRule = rules.getLast(); | |
switch (state) { | |
case CLASSNAME: { | |
newestRule.addMember(currentValue); | |
break; | |
} | |
case ATTRIBUTEVALUE: { | |
var newestMember = newestRule.getNewestMember(); | |
var newestCondition = newestMember.getNewestCondition(); | |
newestCondition.setValue(currentValue); | |
break; | |
} | |
case ATTRIBUTECLOSE: | |
case ATTRIBUTENAME: { | |
var newestMember = newestRule.getNewestMember(); | |
newestMember.addCondition(currentValue); | |
break; | |
} case IDNAME: { | |
var newestMember = newestRule.getNewestMember(); | |
newestMember.setIdName(currentValue); | |
break; | |
} | |
default: | |
quit("Syntax error, could not identify the correct state to store the value"); | |
} | |
} | |
public List<CSSRule> getRules() { | |
return rules.stream().collect(Collectors.toList()); | |
} | |
public String getSelector() { | |
return selector; | |
} | |
private void assertToken(char token, String expected, String message, int index) { | |
if (expected.charAt(0) != token) { | |
quit(message.trim() + String.format(" at column %d", index)); | |
} | |
} | |
private void quit(String message) { | |
System.out.println(message); | |
System.exit(1); | |
} | |
} | |
public static void main(String[] args) { | |
var teste = new Teste(); | |
teste.test(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment