I want to allow name and message customisation in both my lexer and parsers. The parser/lexer must be able to select a specific message or name, e.g.:
messageManager.tokenName.eofToken
Here, eofToken would be a String. tokenName would be a set of names for lexer tokens and messageManager would be a structure like { parserMessage, lexerMessage, contextName, tokenName }
However, I want the message customisation very directly constructed. I don't want something like:
TokenName tokenName = new TokenName();
tokenName.eofToken = "end of file";
tokenName.identifier = "identifier";
tokenName.keyword = "keyword";
tokenName.regExpLiteral = "regexp' literal";
// much more...
I want something like:
new TokenName(
"end of file",
"identifier",
"keyword",
...
)
I know we just need to define parameters and assign them to the corresponding variables, but I don't want a huge constructor like the one I had in my previous parser:
public TokenNames(
String booleanLiteral,
String eofToken,
String identifier,
String punctuator,
String keyword,
String numericLiteral,
String nullLiteral,
String regExpLiteral,
String stringLiteral,
String xmlName,
String xmlMarkup,
String xmlPunctuator,
String xmlTagCharacters,
String xmlText
)
{
this.booleanLiteral = booleanLiteral;
this.eofToken = eofToken;
this.identifier = identifier;
this.punctuator = punctuator;
this.keyword = keyword;
this.numericLiteral = numericLiteral;
this.nullLiteral = nullLiteral;
this.regExpLiteral = regExpLiteral;
this.stringLiteral = stringLiteral;
this.xmlName = xmlName;
this.xmlMarkup = xmlMarkup;
this.xmlPunctuator = xmlPunctuator;
this.xmlTagCharacters = xmlTagCharacters;
this.xmlText = xmlText;
}
I believe it's possible with arrays or varargs (more readable). How to?
Define class like this:
public class Token {
private String booleanLiteral;
private String eofToken;
...
public Token withBooleanLiteral(String booleanLiteral) {
this.booleanLiteral = booleanLiteral;
return this;
}
public Token withEofToken(String eofToken) {
this.eofToken = eofToken;
return this;
}
...
}
You'll get
Token token = new Token()
.withBooleanLiteral("something");
Check out Lombok library and #Wither annotation. It does everything for you.
Of course, the builder pattern is the most obvious solution in here (and the correct one).
But I would like to draw your attention to the fact there is a lot of fields that can be encapsulated by their own classes.
The following fields can be collected into an XMLDescription class:
String xmlName;
String xmlMarkup;
String xmlPunctuator;
String xmlTagCharacters;
String xmlText;
The next ones can be grouped by a LiteralDescription class:
String numericLiteral;
String nullLiteral;
String regExpLiteral;
String stringLiteral;
Think the problem over once more: if there is a chance to shorten a number of the fields to 3 (an extreme bound according to good practices), the constructor can be used instead of the builder.
I believe it's possible with arrays or varargs (more readable).
Please, don't do that - it's an error-prone approach. You are coupling an index of the array with a corresponding field. Such code, hard to maintain and document, causes an API user
to read a documentation if any provided,
to poke around in the sources if no provided,
to follow to any API change believing that nothing is changed.
Related
I am relatively new to Java and would like to know how to store variables separately from a single line of user input.
At the minute the user is prompted to enter football results in the following format
home_name : away_name : home_score : away_score
and I am using a while loop to continue to ask user for input until they enter "stop"
(while (input != "stop))
Once the loop is broken I would like my program to output a variety of data such as total games played, but I'm struggling to store the home_name, away_name etc.. especially if the user wishes to enter multiple lines of results.
Two mainstream ways to store a "record" are:
Maps
Data objects
A map is more generic:
Map<String,String> match = new HashMap<>();
match.put("home_name", "Alvechurch Villa");
match.put("away_name", "Leamington");
match.put("home_score", "0");
match.put("away_score", "6");
You can add a map to a list:
List<Map<String,String>> matches = new ArrayList<>();
matches.add(list);
... and retrieve them:
Map<String,String> match = matches.get(0);
System.out.println(match.get("away_score"));
A data object is more tuned to your data format, but you have to write the class yourself.
public class Match {
public String homeName;
public String awayName;
public int homeScore;
public int awayScore;
}
Now you can use this class:
Match match = new Match();
match.homeName = "Studley";
// etc.
You can add and retrieve these from lists too:
List<Match> matches = new ArrayList<>();
matches.add(match);
Match aMatch = matches.get(0);
This is simple, but it's considered bad practice to have public fields like this - it's better to get at them via methods. For brevity, here's a data class with only one field:
public class Player {
private String name;
public Player(String name) {
this.name = name;
}
public String name() {
return name;
}
}
Player neilStacey = new Player("Neil Stacey");
You can use the same technique with all the fields in Match.
(A common style is to name a method like this getName(), and also to have a setName(). I have used a different style and made the object immutable, in an effort to set a good example!)
One advantage of the data object is that it has different types for different fields: homeName is a String, homeScore is an integer. All the fields in the Map are Strings. You can get around this by using Map<String,Object> but then as a consumer you have to cast to the right type when you read.
String homeName = (String) match.get("home_name");
Data objects allow the compiler to do a lot of compile-time checking that helps you know your code is correct. If you use a map, you won't find out until runtime.
Prompt the user separately for each input.
System.out.println("home_name: ");
String hN = scan.next();
System.out.println("away_name: ");
String aN = scan.next();
System.out.println("home_score: ");
String hS = scan.next();
System.out.println("away_score: ");
String aS = scan.next();
I have a method that takes in a JSON and takes out the data and distributes it to various strings so that they can be set in an entity and persisted. My example below is quite simple but for my actual code I have about 20+ fields
For example see
public Projects createProject(JsonObject jsonInst) {
Projects projectInst = new Projects();
String pId = jsonInst.get("proId").getAsString();
String pName = jsonInst.get("proName").getAsString();
String pStatus = jsonInst.get("proStatus").getAsString();
String pCustId = jsonInst.get("proCustId").getAsString();
String pStartDate = jsonInst.get("proStartDate").getAsString();
...
//Set the entity data
projectInst.setProjectId(pId);
projectInst.setProjectName(pName);
...
Notice if a varible dosent have a corrosponding entry in the Json this code will break with null pointer exception. Obviously I need to validate each parameter befopre calling .getAsString()
What is the best way to do this from a readability point of view I could create 2 varibles for each parameter and check and set for example.
if(jsonInst.get("proName")){
String pName = jsonInst.get("proName").getAsString();
}
Or should I wait for it to be set
if(!pName.isEmpty()){
projectInst.setName(pName)
}
...
Which of these do you think is the best parameter to use for preventing errors.
Is there a way to handle if something is set on a large scale so that I can reduce the amount of code I have to write before I use that varible?
You can create a method that will take field name as parameter and will return json value for that field :
private String getJSONData(String field,JsonObject json){
String data=null;
if(json.has(field)){
data=json.get(field).getAsString();
}
return data;
}
you can call this method for each of your field:
String pId = getJSONData("proId",jsonInst);
By this way you can not only escape NullPointerException, but also avoid code repetition.
An example is as follows:
SEG1|asdasd|20111212|asdsad
SEG2|asdasd|asdasd
SEG3|sdfsdf|sdfsdf|sdfsdf|sdfsfsdf
SEG4|sdfsfs|
Basically, each SEG* line needs to be parsed into a corresponding object, defining what each of those fields are. Some, such as the third field in SEG1 will be parsed as a Date.
Each object will generally stay the same but there may be instances in which an additional field may be added, like so:
SEG1|asdasd|20111212|asdsad|12334455
At the moment, I'm thinking of using the following type of algorithm:
List<String> segments = Arrays.asList(string.split("\r"); // Will always be a CR.
List<String> fields;
String fieldName;
for (String segment : segments) {
fields = Arrays.asList(segment.split("\\|");
fieldName = fields.get(0);
SEG1 seg1;
if (fieldName.compareTo("SEG1") == 0) {
seg1 = new Seg1();
seg1.setField1(fields.get(1));
seg1.setField2(fields.get(2));
seg1.setField3(fields.get(3));
} else if (fieldName.compareTo("SEG2") == 0) {
...
} else if (fieldName.compareTo("SEG3") == 0) {
...
} else {
// Erroneous/failure case.
}
}
Some fields may be optional as well, depending on the object being populated. My concern is if I add a new field to a class, any checks that use the expect field count number will also need to be updated. How could I go about parsing the rows, while allowing for new or modified field types in the class objects to populate?
If you can define a common interface for all to be parsed classes I would suggest the following:
interface Segment {}
class SEG1 implements Segment
{
void setField1(final String field){};
void setField2(final String field){};
void setField3(final String field){};
}
enum Parser {
SEGMENT1("SEG1") {
#Override
protected Segment parse(final String[] fields)
{
final SEG1 segment = new SEG1();
segment.setField1(fields[0]);
segment.setField1(fields[1]);
segment.setField1(fields[2]);
return segment;
}
},
...
;
private final String name;
private Parser(final String name)
{
this.name = name;
}
protected abstract Segment parse(String[] fields);
public static Segment parse(final String segment)
{
final int firstSeparator = segment.indexOf('|');
final String name = segment.substring(0, firstSeparator);
final String[] fields = segment.substring(firstSeparator + 1).split("\\|");
for (final Parser parser : values())
if (parser.name.equals(name))
return parser.parse(fields);
return null;
}
}
For each type of segment add an element to the enum and handle the different kinds of fields in the parse(String[])method.
You can use collections, e.g. ArrayList
You can use var-args
If you want to make it extensible, you may want to process each segment in a loop, instead of handling each occurance.
I would add a header row to your file format with the names of the fields being stored in the file so it looks something more like this:
(1) field1|field2|field3|field4|field5
(2) SEG1|asdasd|20111212|asdsad|
(3) SEG2|asdasd||asdasd|
(4) SEG3|sdfsdf|sdfsdf|sdfsdf|sdfsfsdf
(5) SEG4|sdfsfs|||
This is common for CSV files. I've also added more delimiters so that each line has five 'values'. This way a null value can be specified by just entering two delimiters in a row (see the third row above for an example where a null value is not the last value).
Now your parsing code knows what fields need to be set and you can call the setters using reflection in a loop. Pseudo code:
get the field names from the first line in the file
for (every line in the file except the first one) {
for (every value in the line) {
if (the value is not empty) {
use reflection to get the setter for the field and invoke it with the
value
}
}
}
This allows you to extend the file with additional fields without having to change the code. It also means you can have meaningful field names. The reflection may get a bit complicated with different types e.g. int, String, boolean etc. so I would have to say that if you can, follow #sethu's advice and use a ready-built proven library that does this for you.
Is there a necessity to use the same string with | as a delimiter? If the same classes are used to create the String, then its an ideal case for Xstream. Xstream will convert your java object into XML and back. Xstream will take care of the scenario where some fields are optional. You will not have write any code that parses your text. Here's a link:
http://x-stream.github.io/
In Java I can do something like this:
enum Country {
IRELAND("Europe"),
FRANCE("Europe"),
NIGERIA("Africa"),
THAILAND("Asia");
private String continent;
Country(String continent) {
this.continent = continent;
}
public String getContinent() {
return continent;
}
}
which allows me to do something like:
Country country1 = getCountryFromSomewhere();
Country country2 = Country.FRANCE;
System.out.print("country1 is in " + country1.getContinent());
System.out.print("country2 is in " + country2.getContinent());
Is it possible to do the same thing in VB.NET i.e. add the continent attribute to the country enum?
(Apologies for using C# throughout - I believe the concepts are more about .NET than the language you happen to use; hopefully you're better at reading C# than I am at writing VB.)
Not directly - enums in .NET are just integer types with names for some of the values.
The closest you can come in .NET is to create a type with a fixed set of values. For example, in your case:
public sealed class Country
{
public static readonly Country Ireland = new Country("Europe");
public static readonly Country France = new Country("Europe");
public static readonly Country Nigeria = new Country("Africa");
public static readonly Country Thailand = new Country("Asia");
private readonly string continent;
public string Continent { get { return continent; } }
private Country(string continent)
{
this.continent = continent;
}
}
(I assume the VB.NET would be very similar.)
Note that this doesn't let you switch on the enum values.
If you want polymorphism, you can create nested subclasses which can still call the private constructor, which prevents any other subclasses being created.
One alternative to this is to use attributes on normal enums:
[AttributeUsageAttribute(AttributeTargets.Field)]
public class ContinentAttribute : Attribute
{
// etc
}
public enum Country
{
[Continent("Europe")] Ireland = 1,
[Continent("Europe")] France = 2,
...
}
You'd then need to use reflection to get at the ContinentAttribute and retrieve the string.
Note that here there isn't really a fixed set of values - you could write:
Country country = (Country) 15;
At that point you can't get the continent for it, and if you pass it to any methods which expect it to be a real country, you've got problems. That isn't the case with the earlier solution, where you really are restricted to those few values (and null).
Here is the code:
Imports System.ComponentModel
Imports System.Reflection
Public Enum enumOrderStatus
<Description("None")>
None
<Description("Sent")>
Sent
<Description("Accepted")>
Accepted
<Description("Cancelled")>
Cancelled
<Description("Declined")>
Declined
End Enum
Public Function GetEnumDescription(ByVal EnumConstant As [Enum]) As String
Dim fi As FieldInfo = EnumConstant.GetType().GetField(EnumConstant.ToString())
Dim aattr() As DescriptionAttribute = DirectCast(fi.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
If aattr.Length > 0 Then
Return aattr(0).Description
Else
Return EnumConstant.ToString()
End If
End Function
I used this solution instead:
Declare enum:
Private Enum Country
IRELAND
FRANCE
THAILAND
End Enum
Declare and initialise Dictionary (aka a map):
Dim countryContinentMap As IDictionary(Of Country, String) = New Dictionary(Of Country, String)
countryContinentMap.add(Country.IRELAND, "Europe")
countryContinentMap.add(Country.FRANCE, "Europe")
countryContinentMap.add(Country.THAILAND, "Asia")
which allows me to get the continent like this:
Dim franceContinent As String = countryContinentMap(Country.FRANCE)
Here is how I solved this in my application. Still looking for something even easier.
What do you think about it?
Public Sub Init()
Dim values() As Integer = CType([Enum].GetValues(GetType(MyEnum)), Integer())
For i As Integer = 0 To values.Count - 1
Me.contextMenuInGUI.Items.Add(Me.GetEnumDescription(i))
Next
End Sub
Private Function GetEnumDescription(ByVal i As Integer) As String
Select Case i
Case MyEnum.Comment
Return "Description for Comment"
Case MyEnum.SomeEnumValueInCamelCase
Return "Value without camel case (€)(%)(#)"
End Select
Return "Add a case in Class:GetEnumDescription"
End Function
Create an extension method for your Enum
Usage example:
dim description = TableTag.Important.GetDescription()
Definition example:
Imports System.ComponentModel
Imports System.Reflection
Imports System.Runtime.CompilerServices
Namespace Foo
Public Enum TableTag
<Description("Identifies tables that should be availible for writing as table or view to the model database")>
Important
<Description("Example for a table group that helps to select disctinct tables")>
CustomGroup
End Enum
Public Module TableTagExtensions
<Extension>
Public Function GetDescription(enumValue As TableTag) As String
Dim fieldInfo As FieldInfo = enumValue.GetType().GetField(enumValue.ToString())
Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
If attributes.Length > 0 Then
Return attributes(0).Description
Else
Return enumValue.ToString()
End If
End Function
End Module
End Namespace
I'm looking for a tool which will allow me use command-line-style (preferably POSIX) strings to initialize an object' properties and attributes.
For example, you'd provide it with String input formatted like so:
String input = "--firstName=John --MiddleName=\"Louis Victor\" --lastName=Smith";
... and it would setFirstName("John"), setMiddleName("Louis Victor") and setLastName("Smith") on a given object. (which could be a JavaBean)
Please note that the input is a single String, not an array String[] as is the case with many popular CLI argument "parsers".
This is all similar to args4j but I couldn't get that to work... and I'm hoping to avoid using #annotations.
Does anyone have code/libraries/tools which could accomplish this?
For your use case, forget regular CLI parsers, you need a custom-tailored solution. If you really have such a simple argument syntax (parameters always begin with --, no occurrences of -- in the parameter values), you can use a simple Guava-based solution like this class:
Parse the String Arguments
public class ArgParser{
// split on (optional whitespace) + "--"
private final Splitter paramSplitter = Splitter.on(
Pattern.compile("\\s*\\-{2}")).omitEmptyStrings();
// find key=value (with optional double quotes around value)
private final Pattern keyValuePattern = Pattern
.compile("(.+?)=\"?(.*?)\"?$");
public Map<String, String> getParamValues(final String posixString){
final Map<String, String> paramValues = Maps.newLinkedHashMap();
Matcher matcher;
for(final String param : paramSplitter.split(posixString)){
matcher = keyValuePattern.matcher(param);
if(!matcher.find()){
throw new IllegalArgumentException("Bad parameter: " + param);
}
paramValues.put(matcher.group(1), matcher.group(2));
}
return paramValues;
}
}
Usage
final String input =
"--firstName=John --middleName=\"Louis Victor\" --lastName=Smith";
System.out.println(new ArgParser().getParamValues(input));
Output
{firstName=John, middleName=Louis Victor, lastName=Smith}
Now you can take the map and use it with a Bean library like commons-beanutils (I prefer the Spring BeanWrapper personally, but that only makes sense if you use Spring anyway)
Define the Bean Class
Any way, I'll use this value holder class:
public class Name{
private String firstName;
private String middleName;
private String lastName;
#Override
public String toString(){
return Objects
.toStringHelper(this)
.add("first name", firstName)
.add("middle name", middleName)
.add("last name", lastName)
.toString();
}
// + getters & setters
}
Set the Bean Properties
Now we'll use BeanUtils.populate(Object, Map) to apply the parameter values, like this:
final String input =
"--firstName=John --middleName=\"Louis Victor\" --lastName=Smith";
final Map<String, String> paramValues =
new ArgParser().getParamValues(input);
final Name name = new Name();
BeanUtils.populate(name, paramValues);
System.out.println(name);
Output:
Name{first name=John, middle name=Louis Victor, last name=Smith}
Caveat: Supported Property Types
BeanUtils.populate() supports setting the following property types:
... String, boolean, int, long, float, and double.
In addition, array setters for these
types (or the corresponding primitive
types) can also be identified.
Source: BeanUtilsBean.populate(Object, Map)
If you need parameter conversion beyond that, you should probably look into using the Spring BeanWrapper after all, it's extremely powerful, has many built-in property editors and you can add custom property editors. Just change the code like this:
final Name name = new Name();
final BeanWrapper wrapper = new BeanWrapperImpl(name);
wrapper.setPropertyValues(paramValues);
Reference:
BeanWrapper
PropertyAccessor.setPropertyValues(Map)
If I understand correctly, you are looking for a Java library to parse POSIX-style command line parameters. I used JSAP some time ago and it was really cool (it was using XML configuration back then).
This
-firstName John -lastName Smith
is no POSIX, you mean
--firstName John --lastName Smith
This may be the reason, why you can't get it working.
Update:
As I look at the example, it doesn't look like it could be the reason.