Archive for Sierpień 30th, 2010

W pewnym projekcie, miałem mały problem związany z przejrzystością kodu – mianowicie w kodzie przewijały się różnego rodzaju strategie przechodzenia po elementach:

  • kolekcje (java.util.Collection) – for-each
  • enumeracja (java.util.Enumeration) – for z licznikiem i metoda hasMore elements
  • listy node’ów (org.w3c.dom.NodeList) – for z licznikiem i metoda getLength wyznaczająca koniec

Dodatkowym problemem zaciemniającym kod w przypadku ostatniego elementu jest fakt, że metoda NodeList.item zwraca ZAWSZE node’a, nawet jeżeli użytkownik WIE, że wszystkie node’y będą typu Element i tak musi je sobie zrzutować.

Rozwiązaniem problemu było dodanie metody szablonowej, która na wejsciu przyjmie obiekt, po ktorym będzie sobie chodzić iterator i będzie go zwracać, no to do dzieła:

package pl.bedkowski.util;

import java.util.Enumeration;
import java.util.Iterator;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class IterGenerator {

	public static <T> Iterable<T> iter(final Enumeration<T> e) {
		return new Iterable<T>() {

			public Iterator<T> iterator() {
				return new Iterator<T>() {

					public boolean hasNext() {
						return e.hasMoreElements();
					}

					public T next() {
						return e.nextElement();
					}

					public void remove() {
						throw new UnsupportedOperationException();
					}

				};
			}
		};

	}
}

Tutaj jak widać sprawa była prosta – interfejs Enumeration jest szablonem, więc zamiana jednego na drugi odbyła się w miarę bezboleśnie. Z NodeListą już nie pójdzie tak łatwo, no ale do dzieła :)

	public static <T extends Node> Iterable<T> iter(final NodeList e, Class<T> clazz) {
		final int size = e.getLength();
		return new Iterable<T>() {

			public Iterator<T> iterator() {
				return new Iterator<T>() {

					private int current = 0;

					public boolean hasNext() {
						return current < size;
					}

					@SuppressWarnings("unchecked")
					public T next() {
						return (T) e.item(current++);
					}

					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}

Jak widać w tym przypadku już nie jest tak pięknie, potrzeba dodać adnotację, żeby ukryć warninga, ale przynajmniej użytkownik nie musi sobie tym zaprzątać głowy. Dodajmy jeszcze małe ułatwienie:

	public static Iterable<Node> iter(final NodeList e) {
		return iter(e, Node.class);
	}

I teraz tylko jakiś przykładzik:


import static pl.bedkowski.util.IterGenerator.iter;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Example {
	public static void main(String[] args) {
		NodeList list = null; // wiem, wiem NPE, ale nie mam zadnej list pod reka
		for(int i=0;i<list.getLength();i++) {
			Node n = list.item(i);
			System.out.println(n.getLocalName());
		}

		// teraz wykorzsytamny nasz iter
		for(Node n : iter(list)) {
			System.out.println(n.getLocalName());
		}

		//teraz gdy wiemy, ze lista zawiera same obiekty typu Element
		for(int i=0;i<list.getLength();i++) {
			Element e = (Element) list.item(i);
			System.out.println(e.getTagName());
		}

		// z wykorzytaniem itera
		// teraz wykorzsytamny nasz iter
		for(Element n : iter(list, Element.class)) {
			System.out.println(n.getTagName());
		}
	}

}

Jak widać pracy wcale nie trzeba włożyć dużo a korzyść spora :)