views:

184

answers:

4

We have an ArrayList of items in several classes which are giving me trouble every time I'd like to insert a new item into the list. It was a mistake on my part to have designed the classes in the way I did but changing the design now would be more headache than it's worth (bureaucratic waterfall model.) I should have anticipated format changes to the documents the customer was supplying us waterfall be damned.

I'd like to write a simple script in python which goes into a class, adds the item to the list, and then increments all retrievals for the following items. That doesn't sound very explanatory:

Foo extends Bar{
    public Foo(){
        m_Tags.add("Jane");
        m_Tags.add("Bob");
        m_Tags.add("Jim");
    }

    public String GetJane() { return m_ParsedValue.get( m_Tags.get(1) ); }
    public String GetBob() { return m_ParsedValue.get( m_Tags.get(2) ); }
    public String GetJim() { return m_ParsedValue.get( m_Tags.get(3) ); }
}

You see if I want to add a value between "Jane" and "Bob" I then have to increment the integers in the Get* functions. I just want to write a simple script in Python that does the work for me. Someone I very much respect suggested regex.

Edit:

Yes, LinkedHashMap. So simple, so easy and so not in the design specs now. I hate waterfall. Hate it with a passion. This whole bit was a "small" and "easy" part that "shouldn't take much time to design." I made mistakes. It's stuck in stone now.

+4  A: 

Don't do this with regexp. Create symbolic constants (using for example an enum) that map the names to numbers.

schot
Technically, if I had to redesign from the beginning I would have made a named variable for each component, assigned those to a list, and then passed that list to the parsing functions. Then I wouldn't need to deal with all these magic numbers.
wheaties
You don't need a redesign. Just add the constants now. Then have the script, if you must use it, increment the constants.
vy32
A: 

I'm doing this (well, something very similar) right now, but using Excel and VBA macros. All of the Business Values are organized and ordered in a spreadsheet. I just have to click a button to generate the appropriate code for the selected cells, then copy-paste to the IDE. Better yet, I have several "code columns" for each row. Some of them generate queries, some XSL transformations, and some procedures. For one row of business data, I can get all three types of generated code very easily.

I found this (re-generate) to be MUCH easier than re-formatting the existing code I had.

FrustratedWithFormsDesigner
This sounds like a nightmare of a system.
matt b
@matt b: At first I thought so too. The Analysts maintain the business data in the spreadsheets (they're actually quite well organized), so I can easily take a copy of them and then apply my macros to get lots of boiler-plate code that rarely needs manual tweaking. It's very fast for development and matches EXACTLY with the business requirements - this also helps find errors in the requirements.
FrustratedWithFormsDesigner
+4  A: 

You want your regular expression to be as flexible as the compiler will be with respect to whitespace between tokens. Doing so and mimicking whitespace usage makes the pattern pretty messy. The code below (sorry: Perl, not Python) edits your source files in-place.

#! /usr/bin/perl -i.bak    
use warnings;
use strict;
my $template =
  '^( public
      String
      Get)(\w+)( \( \) { return
        m_ParsedValue . get \( m_Tags . get \( )(\d+)( \) \) ; } )$';
$template =~ s/ +/\\s*/g;
$template =~ s/(\r?\n)+/\\s+/g;
my $getter = qr/$template/x;

die "Usage: $0 after new-name source ..\n" unless @ARGV >= 3;
my $after = shift;
my $add   = shift;
my $index;
while (<>) {
  unless (/$getter/) {
    print;
    next;
  }
  my($abc,$name,$lmno,$i,$xyz) = ($1,$2,$3,$4,$5);
  if (defined $index) {
    print join "" => $abc, $name, $lmno, ++$index, $xyz;
  }
  else {
    if ($name eq $after) {
      $index = $i;
      print; print join "" => $abc, $add, $lmno, ++$index, $xyz;
    }
    else { print; }
  }
}

For example,

$ ./add-after Jane Foo code.java
$ cat code.java
Foo extends Bar{
    public Foo(){
        m_Tags.add("Jane");
        m_Tags.add("Bob");
        m_Tags.add("Jim");
    }

    public String GetJane() { return m_ParsedValue.get( m_Tags.get(1) ); }
    public String GetFoo() { return m_ParsedValue.get( m_Tags.get(2) ); }
    public String GetBob() { return m_ParsedValue.get( m_Tags.get(3) ); }
    public String GetJim() { return m_ParsedValue.get( m_Tags.get(4) ); }
}
Greg Bacon
Thank you very much. I can show this to someone who knows perl and python. They'll be able to guide me back.
wheaties
You're welcome. I'm glad it helps.
Greg Bacon
+4  A: 

Comments about bad-pratices apart - here is the code you asked in the language you asked for. The best thign if you are keeping the system this way, probably would be to make these java files be authomatically generated in the build proccess itself -- you 'd just keep a names list in a .txt file in the directory. This sript is suitable to do that.

(It won't modify your files, it genrate new ones based on the template you posted here)

import re, sys

template = """Foo extends Bar{
    public Foo(){
%s
    }

%s
}
"""

tag_templ =   """        m_Tags.add("%s");"""
getter_templ = """    public String GetJane() { return m_ParsedValue.get( m_Tags.get(%d) ); }"""

def parse_names(filename):
    data = open(filename).read()
    names = re.findall(r'm_Tags\.add\("(.*?)"', data)
    return names

def create_file(filename, names):
    tag_lines = [tag_templ % name for name in names]
    getter_lines = [getter_templ % (i + 1) for i in range(len(names))]
    code = template % ("\n".join(tag_lines), "\n".join(getter_lines))
    file = open(filename,"wt")
    file.write(code)
    file.close()

def insert_name(after, new_name, names):
    names.insert(names.index(after) + 1, new_name)

if __name__ == "__main__":
    if len(sys.argv ) < 4:
        sys.stderr.write("Usage: changer.py <filename> <name-before-insertion> <new-name>")
        sys.exit(1)
    filename, name_before, new_name = sys.argv[1:]
    names = parse_names(filename)
    insert_name(name_before, new_name, names)
    create_file(filename, names)
jsbueno
Thanks for this and thanks for not commenting on my mistakes. That's really a great idea. I've been trying to move towards a more pragmatic approach (just finished pragmatic programmer a month ago.)
wheaties
A template is absolutely the right way to go. But why not take it a step further, and use a real templating library such as Template Toolkit for Python? http://tt2.org/python/index.html
daotoad
Becasue tehre is no need to do so in such a simple example? One of Python motos is "simple is better than complex" - and even for real code, there are good enough template formatting methods in the standard libs themselves to justify external dependencies for such a simple case.
jsbueno